探秘react,一文弄懂react的基本使用和高级特性
一文詳解react的基本使用、高級特性和周邊插件
- ?序言
- 📝一、React的基本使用
- 1、JSX基本使用
- (1)變量、表達式
- (2)class和style
- (3)子元素和組件
- (4)原生 html
- 2、條件判斷
- (1)if else
- (2)三元表達式
- (3)邏輯運算符 && ||
- 3、渲染列表
- (1)map 和 key
- 4、React的事件
- (1)bind this
- (2)關于 event 參數
- (3)傳遞自定義參數
- (4)注意點
- 5、表單
- (1)受控組件
- (2)input textarea select 用value
- (3)checkbox radio 用 checked
- 6、組件使用
- (1)props 傳遞數據
- (2)props 傳遞函數
- (3)props 類型檢查
- 7、setState
- (1)不可變值
- (2)可能是異步更新
- (3)可能會被合并
- 8、組件生命周期
- 📖二、React高級特性
- 1、函數組件
- 2、非受控組件
- (1)input
- (2)checkbox
- (3)file
- (4)總結梳理
- 3、Protals
- (1)為什么要用 Protals ?
- (2)如何使用
- (3)使用場景
- 4、context
- (1)使用場景
- (2)舉例闡述
- 5、異步組件(懶加載)
- 6、性能優化
- (1)shouldComponentUpdate(簡稱SCU)
- (2)PureComponent和React.memo
- (3)不可變值
- 7、關于組件公共邏輯的抽離
- (1)高階組件 HOC
- (2)Render Props
- (3)HOC vs Render Props
- 📚三、Redux和React-router
- 1、Redux
- (1)Redux概念簡述
- (2)Redux的工作流程
- (2)react-redux
- (3)異步action
- (4)Redux數據流圖
- 2、React-router
- (1)路由模式
- (2)路由配置
- Ⅰ. 動態路由
- Ⅱ. 懶加載
- 🗞?四、結束語
?序言
對于剛學習 react 的小伙伴來說,總是從基礎開始學習,周一自然也不例外捏。那在下面的文章中,將講解 react 的基本使用和高級特性,更有周邊插件 Redux 和 React-router 待你來探尋。
在本文中,融合大量案例🌰和動圖🕹?進行展示。可以把它當成是 react 的入門寶庫,有不懂的語法知識點時或許在這里可以尋找到你的答案并且通過例子運用起來。
叮,廢話不多說,下面來開始探索 react 的奧秘吧👏
📝一、React的基本使用
1、JSX基本使用
(1)變量、表達式
在 react 中,最基礎的內容便是變量和表達式,具體形式如下:
第一種類型:獲取變量、插值
import React from 'react'class JSXBaseDemo extends React.Component {constructor(props) {super(props)this.state = {name: '掘金:星期一研究室',imgUrl: 'https://p3-passport.byteacctimg.com/img/user-avatar/cc88f43a329099d65898aff670ea1171~300x300.image',flag: true}}render() {// 獲取變量 插值const pElem = <p>{this.state.name}</p>return pElem} }export default JSXBaseDemo注意, react 中插值的形式是單個花括號 {} 的形式。最終瀏覽器顯示的效果如下:
第二種類型:表達式
import React from 'react'class JSXBaseDemo extends React.Component {constructor(props) {super(props)this.state = {name: '掘金:星期一研究室',imgUrl: 'https://p3-passport.byteacctimg.com/img/user-avatar/cc88f43a329099d65898aff670ea1171~300x300.image',flag: true}}render() {// 表達式const exprElem = <p>{this.state.flag ? 'yes' : 'no'}</p>return exprElem} }export default JSXBaseDemoreact 中也支持直接在插值里面使用表達式,如上述代碼中的 this.state.flag ? 'yes' : 'no' 。最終瀏覽器的顯示效果如下:
(2)class和style
通常情況下,如果我們要給某一個標簽設置類名,那么會給該標簽加上一個 class 。而在 react 中,如果想要給一個標簽加上一個類,那么需要給其加上 className 。具體代碼如下:
import React from 'react' import './style.css' import List from '../List'class JSXBaseDemo extends React.Component {render() {// classconst classElem = <p className="title">設置 css class</p>// styleconst styleData = { fontSize: '30px', color: 'blue' }const styleElem1 = <p style={styleData}>設置 style</p>// 內聯寫法,注意 {{ 和 }}const styleElem2 = <p style={{ fontSize: '30px', color: 'blue' }}>設置 style</p>// 返回結果return [ classElem, styleElem1, styleElem2 ]} }export default JSXBaseDemo此時瀏覽器的顯示效果為:
同時需要注意的是,在 react 中,如果要在標簽里面寫內聯樣式,那么需要使用雙花括號 {{}} 來表示。
(3)子元素和組件
第一種類型:子元素
對于子元素來說,它可以在標簽里面進行使用。如下代碼所示:
import React from 'react'class JSXBaseDemo extends React.Component {constructor(props) {super(props)this.state = {name: '掘金:星期一研究室',imgUrl: 'https://p3-passport.byteacctimg.com/img/user-avatar/cc88f43a329099d65898aff670ea1171~300x300.image',flag: true}}render() {// 子元素const imgElem = <div><p>我的頭像</p><img src="xxxx.png"/><img src={this.state.imgUrl}/></div>return imgElem} }export default JSXBaseDemo最終,瀏覽器的顯示效果為:
第二種類型:加載組件
如果要在 React 中加載一個組件,那么我們可以這么處理。具體代碼如下:
import React from 'react' import './style.css' import List from '../List'class JSXBaseDemo extends React.Component {constructor(props) {super(props)this.state = {name: '掘金:星期一研究室',imgUrl: 'https://p3-passport.byteacctimg.com/img/user-avatar/cc88f43a329099d65898aff670ea1171~300x300.image',flag: true}}render() {// 加載組件const componentElem = <div><p>JSX 中加載一個組件</p><hr/><List/></div>return componentElem} }export default JSXBaseDemoList.js 組件的代碼如下:
import React from 'react'class List extends React.Component {constructor(props) {super(props)this.state = {name: 'React',list: ['a', 'b', 'c']}}render() {return <div><p onClick={this.changeName.bind(this)}>{this.state.name}</p><ul>{this.state.list.map((item, index) => {return <li key={index}>{item}</li>})}</ul><button onClick={this.addItem.bind(this)}>添加一項</button></div>}changeName() {this.setState({name: '星期一研究室'})}addItem() {this.setState({list: this.state.list.concat(`${Date.now()}`) // 使用不可變值})} }export default List此時瀏覽器的顯示效果如下:
由此,我們就將一個組件注入到組件當中。
(4)原生 html
繼續,我們來看原生 html 在 react 中是如何使用的。先看以下代碼:
import React from 'react'class JSXBaseDemo extends React.Component {render() {// 原生 htmlconst rawHtml = '<span>富文本內容<i>斜體</i><b>加粗</b></span>'const rawHtmlData = {// 把 rawHtml 賦值給 __html__html: rawHtml // 注意,必須是這種格式}const rawHtmlElem = <div><p dangerouslySetInnerHTML={rawHtmlData}></p><p>{rawHtml}</p></div>return rawHtmlElem} }export default JSXBaseDemo此時瀏覽器的顯示效果如下:
大家可以看到,如果要在 react 中使用原生 html ,那么必須使用 const rawHtmlData = { __html: rawHtml } 這種形式,才能將原生 html 代碼給解析出來。否則的話, react 是無法正常將原生 html 解析出來的。
2、條件判斷
(1)if else
先看下面這段代碼:
import React from 'react' import './style.css'class ConditionDemo extends React.Component {constructor(props) {super(props)this.state = {theme: 'black'}}render() {const blackBtn = <button className="btn-black">black btn</button>const whiteBtn = <button className="btn-white">white btn</button>// if elseif (this.state.theme === 'black') {return blackBtn} else {return whiteBtn}} }export default ConditionDemostyle.css 的代碼如下:
.title {font-size: 30px;color: red; }.btn-white {color: #333; } .btn-black {background-color: #666;color: #fff;; }此時瀏覽器的顯示效果為:
大家可以看到,當我們 theme 設置為 black 時,最終顯示的效果就是黑色。如果我們把 theme 設置為其他狀態,那么最終顯示的效果就是白色。
(2)三元表達式
先來看一段代碼:
import React from 'react' import './style.css'class ConditionDemo extends React.Component {constructor(props) {super(props)this.state = {theme: 'black'}}render() {const blackBtn = <button className="btn-black">black btn</button>const whiteBtn = <button className="btn-white">white btn</button>// 三元運算符return <div>{ this.state.theme === 'black' ? blackBtn : whiteBtn }</div>} }export default ConditionDemo此時瀏覽器的顯示效果為:
大家可以看到,我們也可以通過三元表達式 this.state.theme === 'black' ? blackBtn : whiteBtn 的方式來對一些條件進行判斷。
(3)邏輯運算符 && ||
先來看一段代碼:
import React from 'react' import './style.css'class ConditionDemo extends React.Component {constructor(props) {super(props)this.state = {theme: 'black'}}render() {const blackBtn = <button className="btn-black">black btn</button>const whiteBtn = <button className="btn-white">white btn</button>// &&return <div>{ this.state.theme === 'black' && blackBtn }</div>} }export default ConditionDemo此時瀏覽器的顯示結果也是和上述一樣的。具體如下:
this.state.theme === 'black' && blackBtn 這句話的意思為,如果 this.state.theme 的值為 black 時,那么返回 backBtn 的結果。
3、渲染列表
(1)map 和 key
先來看一段代碼:
import React from 'react'class ListDemo extends React.Component {constructor(props) {super(props)this.state = {list: [{id: 'id-1',title: '標題1'},{id: 'id-2',title: '標題2'},{id: 'id-3',title: '標題3'}]}}render() {return <ul>{ /* 類似于vue中的v-for */this.state.list.map((item, index) => {// 這里的 key 和 Vue 的 key 類似,必填,不能是 index 或 randomreturn <li key={item.id}>index {index}; id {item.id}; title {item.title}</li>})}</ul>} }export default ListDemo此時瀏覽器的顯示效果如下:
在 react 中,使用的是 list.map 來渲染列表。其中, 需要注意的是, list.map() 是一個函數,那現在我們假設這個函數里面要遵循一套規則 list.map( item => item.id ) 。
這個時候,我們視 .map 為一個數組的重組,那么重組的規則就是 item => item.id 。同時, list.map 返回的是一個數組。
4、React的事件
(1)bind this
先來看一段代碼:
import React from 'react'class EventDemo extends React.Component {constructor(props) {super(props)this.state = {name: 'zhangsan'}// 修改方法的 this 指向// 使用這個寫法,組件初始化時,只執行一次bindthis.clickHandler1 = this.clickHandler1.bind(this)}render() {// this - 使用 bindreturn <p onClick={this.clickHandler1}>{this.state.name}</p>}clickHandler1() {// console.log('this....', this) // this 默認是 undefinedthis.setState({name: 'lisi'})} }export default EventDemo最終瀏覽器顯示的效果為:
在這段代碼中,我們通過 this.clickHandler1 = this.clickHandler1.bind(this) 來對 clickHandler1 進行綁定。
還有另外一種關于 this 的綁定方法。先來看一段代碼:
import React from 'react'class EventDemo extends React.Component {constructor(props) {super(props)this.state = {name: 'zhangsan'}}render() {// this - 使用靜態方法return <p onClick={this.clickHandler2}>clickHandler2 {this.state.name}</p>}// 靜態方法,this 指向當前實例clickHandler2 = () => {this.setState({name: 'lisi'})} }export default EventDemo此時瀏覽器的顯示效果為:
對于上面的這種方式來說, clickHandler2 是一個靜態方法,此時它的 this 會指向當前的實例。
(2)關于 event 參數
先來看一段代碼:
import React from 'react'class EventDemo extends React.Component {constructor(props) {super(props)this.state = {name: 'zhangsan'}}render() {// eventreturn <a href="https://imooc.com/" onClick={this.clickHandler3}>click me</a>}// 獲取 event (重點)clickHandler3 = (event) => {event.preventDefault() // 阻止默認行為event.stopPropagation() // 阻止冒泡console.log('target', event.target) // 指向當前元素,即當前元素觸發console.log('current target', event.currentTarget) // 指向當前元素,假象!!!// 注意,event 其實是 React 封裝的。可以把 __proto__.constructor 看成是 SyntheticEvent 組合事件console.log('event', event) // 不是原生的 Event ,原生的是 MouseEventconsole.log('event.__proto__.constructor', event.__proto__.constructor)// 原生 event 如下。其 __proto__.constructor 是 MouseEventconsole.log('nativeEvent', event.nativeEvent)console.log('nativeEvent target', event.nativeEvent.target) // 指向當前元素,即當前元素觸發console.log('nativeEvent current target', event.nativeEvent.currentTarget) // 指向 document !!!} }export default EventDemo此時瀏覽器的顯示效果如下:
依據以上內容,需要注意的點是:
- event 是合成事件 SyntheticEvent ,它能夠模擬出來 DOM 事件所有的能力;
- event 是React 封裝出來的,而 event.nativeEvent 是原生事件對象;
- 所有的事件,都會被掛載到 document 上;
- React 中的事件,和 DOM 事件不一樣,和 Vue 事件也不一樣。
(3)傳遞自定義參數
先來看一段代碼:
import React from 'react'class EventDemo extends React.Component {constructor(props) {super(props)this.state = {name: 'zhangsan',list: [{id: 'id-1',title: '標題1'},{id: 'id-2',title: '標題2'},{id: 'id-3',title: '標題3'}]}}render() {// 傳遞參數 - 用 bind(this, a, b)return <ul>{this.state.list.map((item, index) => {return <li key={item.id} onClick={this.clickHandler4.bind(this, item.id, item.title)}>index {index}; title {item.title}</li>})}</ul>}// 傳遞參數clickHandler4(id, title, event) {console.log(id, title)console.log('event', event) // 最后追加一個參數,即可接收 event} }export default EventDemo此時,瀏覽器的顯示效果為:
大家可以看到,我們通過使用 this.clickHandler4.bind(this, item.id, item.title) 這種形式來對 react 中的事件進行參數傳遞。
(4)注意點
- React 16 將事件綁定到 document 上;
- React 17 將事件綁定到 root 組件上;
- 這樣做的好處在于:有利于多個 React 版本并存,例如微前端。
如下圖所示:
5、表單
(1)受控組件
先來看一段代碼:
import React from 'react'class FormDemo extends React.Component {constructor(props) {super(props)this.state = {name: '星期一研究室',info: '個人信息',city: 'GuangDong',flag: true,gender: 'female'}}render() {// 受控組件return <div><p>{this.state.name}</p><label htmlFor="inputName">姓名:</label> {/* 用 htmlFor 代替 for */}<input id="inputName" value={this.state.name} onChange={this.onInputChange}/></div>}onInputChange = (e) => {this.setState({name: e.target.value})} }export default FormDemo此時瀏覽器的顯示效果如下:
在 react 中,通過使用 onChange 事件來手動修改 state 里面的值。
(2)input textarea select 用value
上面我們已經講解了 input ,接下來我們來看 textarea 和 select 。
先來看 textarea 相關的代碼:
import React from 'react'class FormDemo extends React.Component {constructor(props) {super(props)this.state = {name: '星期一研究室',info: '個人信息',city: 'GuangDong',flag: true,gender: 'female'}}render() {// textarea - 使用 valuereturn <div><textarea value={this.state.info} onChange={this.onTextareaChange}/><p>{this.state.info}</p></div>}onTextareaChange = (e) => {this.setState({info: e.target.value})} }export default FormDemo此時瀏覽器的打印效果是:
同樣地, textarea 也是用 value 和 onChange 來對值進行綁定。
繼續來看 select 。具體代碼如下:
import React from 'react'class FormDemo extends React.Component {constructor(props) {super(props)this.state = {name: '星期一研究室',info: '個人信息',city: 'GuangDong',flag: true,gender: 'female'}}render() {// textarea - 使用 valuereturn <div><textarea value={this.state.info} onChange={this.onTextareaChange}/><p>{this.state.info}</p></div>}onSelectChange = (e) => {this.setState({city: e.target.value})} }export default FormDemo此時,瀏覽器的顯示效果為:
與 input 和 textarea 一樣,也是通過操作 value 和 onChange ,來改變最終的值。
(3)checkbox radio 用 checked
先來看 ckeckbox 。代碼如下:
import React from 'react'class FormDemo extends React.Component {constructor(props) {super(props)this.state = {name: '星期一研究室',info: '個人信息',city: 'GuangDong',flag: true,gender: 'female'}}render() {// checkboxreturn <div><input type="checkbox" checked={this.state.flag} onChange={this.onCheckboxChange}/><p>{this.state.flag.toString()}</p></div>}onCheckboxChange = () => {this.setState({flag: !this.state.flag})} }export default FormDemo此時瀏覽器的顯示效果為:
在上面的代碼中, checkbox 通過操作 checked 和 onChange ,來改變 state 的值。
radio 也是類似,如下代碼所示:
import React from 'react'class FormDemo extends React.Component {constructor(props) {super(props)this.state = {name: '星期一研究室',info: '個人信息',city: 'GuangDong',flag: true,gender: 'female'}}render() {// radioreturn <div>male <input type="radio" name="gender" value="male" checked={this.state.gender === 'male'} onChange={this.onRadioChange}/>female <input type="radio" name="gender" value="female" checked={this.state.gender === 'female'} onChange={this.onRadioChange}/><p>{this.state.gender}</p></div>}onRadioChange = (e) => {this.setState({gender: e.target.value})} }export default FormDemo此時瀏覽器的顯示效果是:
6、組件使用
對于父子組件的使用來說,我們需要明白三個知識點:props 傳遞數據、props 傳遞函數和 props 類型檢查。
先來看一段代碼:
import React from 'react' import PropTypes from 'prop-types'class Input extends React.Component {constructor(props) {super(props)this.state = {title: ''}}render() {return <div><input value={this.state.title} onChange={this.onTitleChange}/><button onClick={this.onSubmit}>提交</button></div>}onTitleChange = (e) => {this.setState({title: e.target.value})}onSubmit = () => {const { submitTitle } = this.propssubmitTitle(this.state.title)this.setState({title: ''})} } // props 類型檢查 Input.propTypes = {submitTitle: PropTypes.func.isRequired }class List extends React.Component {constructor(props) {super(props)}render() {const { list } = this.propsreturn <ul>{list.map((item, index) => {return <li key={item.id}><span>{item.title}</span></li>})}</ul>} } // props 類型檢查 // 通過propTypes可以清楚地知道list需要一個什么類型的數據 List.propTypes = {list: PropTypes.arrayOf(PropTypes.object).isRequired }class Footer extends React.Component {constructor(props) {super(props)}render() {return <p>{this.props.text}{this.props.length}</p>}componentDidUpdate() {console.log('footer did update')}shouldComponentUpdate(nextProps, nextState) {if (nextProps.text !== this.props.text|| nextProps.length !== this.props.length) {return true // 可以渲染}return false // 不重復渲染}// React 默認:父組件有更新,子組件則無條件也更新!!!// 性能優化對于 React 更加重要!// SCU 一定要每次都用嗎?—— 需要的時候才優化(SCU即shouldComponentUpdate) }// 父組件 class TodoListDemo extends React.Component {constructor(props) {super(props)// 狀態(數據)提升// list的數據需要放在父組件this.state = {list: [{id: 'id-1',title: '標題1'},{id: 'id-2',title: '標題2'},{id: 'id-3',title: '標題3'}],footerInfo: '底部文字'}}render() {return <div><Input submitTitle={this.onSubmitTitle}/><List list={this.state.list}/><Footer text={this.state.footerInfo} length={this.state.list.length}/></div>}onSubmitTitle = (title) => {this.setState({list: this.state.list.concat({id: `id-${Date.now()}`,title})})} }export default TodoListDemo此時瀏覽器的顯示效果如下:
依據以上代碼,我們來對 props 的各個類型進行介紹。
(1)props 傳遞數據
最后一個 TodoListDemo 是父組件,其他都是子組件。在 Input 組件和 List 組件中,我們將 props 屬性的內容,以 this.props.xxx 的方式,傳遞給父組件。
(2)props 傳遞函數
React 在傳遞函數這一部分和 vue 是不一樣的。對于 vue 來說,如果有一個父組件要傳遞函數給子組件,子組件如果想要觸發這個函數,那么需要使用事件傳遞和 $emit 的方式來解決。
大家定位到 Input 組件中,在這里,我們將 submitTitle 以函數的形式,傳遞給父組件中的 onSubmitTitle 。
(3)props 類型檢查
大家定位到兩處 props 類型檢查的地方。使用 react 中的 PropTypes ,我們可以對當前所使用的屬性進行一個類型檢查。比如說: submitTitle: PropTypes.func.isRequired 表明的是, submitTitle 是一個函數,并且是一個必填項。
就這樣,通過上面的例子,我們學習了屬性傳遞、屬性驗證以及父組件和子組件之間怎么通過傳事件的形式來進行通信。
7、setState
(1)不可變值
所謂不可變值,即所設置的值永不改變。那這個時候,我們就需要去創建一個副本,來設置 state 的值。
來看幾個要點:
第一點:state 要在構造函數中定義。如下代碼所示:
import React from 'react'// 函數組件,默認沒有 state class StateDemo extends React.Component {constructor(props) {super(props)// 第一,state 要在構造函數中定義this.state = {count: 0}}render() {return <div><p>{this.state.count}</p></div>} }export default StateDemo第二點,不要直接修改 state ,要使用不可變值。如下代碼所示:
import React from 'react'// 函數組件,默認沒有 state class StateDemo extends React.Component {constructor(props) {super(props)// 第一,state 要在構造函數中定義this.state = {count: 0}}render() {return <div><p>{this.state.count}</p><button onClick={this.increase}>累加</button></div>}increase= () => {// 第二,不要直接修改 state,使用不可變值// this.state.count++ // 錯誤寫法,會直接修改原來的值this.setState({count: this.state.count + 1 // ShouldComponentUpdate → SCU})} }export default StateDemo大家可以看到,在上面的代碼中,我們通過 this.state({}) 這種形式,來修改 state 的值。值得注意的是,很多小伙伴會直接使用 this.state.count++ 來修改 state 的值,這在 react 中是非常不允許的。因此,要注意這個要點。
第三點,在 react 中操作數組的值。如下代碼所示:
// 不可變值(函數式編程,純函數) - 數組 const list5Copy = this.state.list5.slice() list5Copy.splice(2, 0, 'a') // 中間插入/刪除 this.setState({list1: this.state.list1.concat(100), // 追加list2: [...this.state.list2, 100], // 追加list3: this.state.list3.slice(0, 3), // 截取list4: this.state.list4.filter(item => item > 100), // 篩選list5: list5Copy // 其他操作 }) // 注意,不能直接對 this.state.list 進行 push pop splice 等,這樣違反不可變值第四點,在 react 中操作對象的值。如下代碼所示:
// 不可變值 - 對象 this.setState({obj1: Object.assign({}, this.state.obj1, {a: 100}),obj2: {...this.state.obj2, a: 100} }) // 注意,不能直接對 this.state.obj 進行屬性設置,即 this.state.obj.xxx 這樣的形式,這種形式會違反不可變值(2)可能是異步更新
react 中的 state ,有可能是異步更新。來看一段代碼:
import React from 'react'class StateDemo extends React.Component {constructor(props) {super(props)this.state = {count: 0}}render() {return <div><p>{this.state.count}</p><button onClick={this.increase}>累加</button></div>}increase= () => {// setState 可能是異步更新(也有可能是同步更新) this.setState({count: this.state.count + 1}, () => {// 聯想 Vue $nextTick - DOMconsole.log('count by callback', this.state.count) // 回調函數中可以拿到最新的 state})console.log('count', this.state.count) // 異步的,拿不到最新值} }export default StateDemo此時瀏覽器的顯示效果為:
大家可以看到,this.state 前半部分并不能同一時間得到更新,所以它是異步操作。而后面的箭頭函數中的內容可以得到同步更新,所以后面函數的部分是同步操作。
值得注意的是, setTimeout 在 setState 中是同步的。來看一段代碼:
import React from 'react'class StateDemo extends React.Component {constructor(props) {super(props)this.state = {count: 0}}render() {return <div><p>{this.state.count}</p><button onClick={this.increase}>累加</button></div>}increase= () => {// setTimeout 中 setState 是同步的setTimeout(() => {this.setState({count: this.state.count + 1})console.log('count in setTimeout', this.state.count)}, 0)} }export default StateDemo此時,瀏覽器的顯示效果為:
還有一個要注意的點是,如果是自己定義的 DOM 事件,那么在 setState 中是同步的,用在 componentDidMount 中。
如果是銷毀事件,那么用在 componentWillMount 生命周期中。代碼如下:
import React from 'react'class StateDemo extends React.Component {constructor(props) {super(props)this.state = {count: 0}}render() {return <div><p>{this.state.count}</p><button onClick={this.increase}>累加</button></div>}bodyClickHandler = () => {this.setState({count: this.state.count + 1})console.log('count in body event', this.state.count)}componentDidMount() {// 自己定義的 DOM 事件,setState 是同步的document.body.addEventListener('click', this.bodyClickHandler)}componentWillUnmount() {// 及時銷毀自定義 DOM 事件document.body.removeEventListener('click', this.bodyClickHandler)// clearTimeout} }export default StateDemo此時瀏覽器的顯示效果為:
(3)可能會被合并
setState 在傳入對象時,更新前會被合并。來看一段代碼:
import React from 'react'class StateDemo extends React.Component {constructor(props) {super(props)this.state = {count: 0}}render() {return <div><p>{this.state.count}</p><button onClick={this.increase}>累加</button></div>}increase= () => {// 傳入對象,會被合并(類似 Object.assign )。執行結果只一次 +1this.setState({count: this.state.count + 1})this.setState({count: this.state.count + 1})this.setState({count: this.state.count + 1})} }export default StateDemo此時瀏覽器的顯示效果為:
有小伙伴可能會覺得,一下子多個三個 setState ,那結果應該是 +3 才是。但其實,如果傳入的是對象,那么結果會把三個合并為一個,最終只執行一次。
還有另外一種情況,如果傳入的是函數,那么結果不會被合并。來看一段代碼:
import React from 'react'class StateDemo extends React.Component {constructor(props) {super(props)this.state = {count: 0}}render() {return <div><p>{this.state.count}</p><button onClick={this.increase}>累加</button></div>}increase= () => {// 傳入函數,不會被合并。執行結果是 +3this.setState((prevState, props) => {return {count: prevState.count + 1}})this.setState((prevState, props) => {return {count: prevState.count + 1}})this.setState((prevState, props) => {return {count: prevState.count + 1}})} }export default StateDemo此時瀏覽器的顯示效果為:
大家可以看到,如果傳入的是函數,那么結果一下子就執行三次了。
8、組件生命周期
react 的組件生命周期,有單組件聲明周期和父子組件聲明周期。其中,父子組件生命周期與 Vue 類似。
這里附上一個生命周期相關的網站:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
下面附上生命周期的圖:
📖二、React高級特性
1、函數組件
我們先來了解 class 組件和函數組件分別是什么樣的。先看 class 組件,代碼如下:
// class 組件 class List extends React.Component {constructor(props) {super(props)}redner() {const { list } = this.propsreturn <ul>{list.map((item, index) => {return <li key={item.id}><span>{item.title}</span></li>})}</ul>} }函數組件的形式如下:
// 函數組件 function List(props) {const { list } = this.propsreturn <ul>{list.map((item, idnex) => {return <li key={item.id}><span>{item.title}</span></li>})}</ul> }現在我們來梳理以下, class 組件和函數組件兩者之間的區別。所謂函數組件,具有以下特點:
- 只是一個純函數,它輸入的是 props ,輸出的是 JSX ;
- 函數組件沒有實例,沒有生命周期,也沒有 state ;
- 函數組件不能擴展其他方法。
相反地, class 組件就擁有函數組件相異的特點。
2、非受控組件
在上述表單模塊,我們談論到了受控組件,那接下來,我們就來談論非受控組件。
所謂非受控組件,就是 input 里面的值,不受到 state 的控制。下面我們先來看幾種場景。
(1)input
先來看一段代碼:
import React from 'react'class App extends React.Component {constructor(props) {super(props)this.state = {name: '星期一研究室',flag: true,}this.nameInputRef = React.createRef() // 創建 ref}render() {// input defaultValuereturn <div>{/* 使用 defaultValue 而不是 value ,使用 ref */}<input defaultValue={this.state.name} ref={this.nameInputRef}/>{/* state 并不會隨著改變 */}<span>state.name: {this.state.name}</span><br/><button onClick={this.alertName}>alert name</button></div>}alertName = () => {const elem = this.nameInputRef.current // 通過 ref 獲取 DOM 節點alert(elem.value) // 不是 this.state.name} }export default App此時瀏覽器的顯示效果為:
大家可以看到,如果是非受控組件,那么需要使用 defaultValue 去控制組件的值。且最終 input 框里面的內容不論我們怎么改變,都不會影響到 state 的值。
(2)checkbox
對于復選框 checkbox 來說,先看以下代碼:
import React from 'react'class App extends React.Component {constructor(props) {super(props)this.state = {name: '星期一研究室',flag: true,}}render() {// checkbox defaultCheckedreturn <div><inputtype="checkbox"defaultChecked={this.state.flag}/><p>state.name: { this.state.flag === true ? 'true' : 'false' }</p></div>} }export default App此時瀏覽器的顯示效果如下:
大家可以看到,復選框如果當非受控組件來使用的使用,那么使用 defaultCkecked 來對值進行控制。同時,我們也看到了,最終不管 checked 的值如何改變, state 的值都不受影響。
(3)file
先來看一段代碼:
import React from 'react'class App extends React.Component {constructor(props) {super(props)this.state = {name: '星期一研究室',flag: true,}this.fileInputRef = React.createRef()}render() {// filereturn <div><input type="file" ref={this.fileInputRef}/><button onClick={this.alertFile}>alert file</button></div>}alertFile = () => {const elem = this.fileInputRef.current // 通過 ref 獲取 DOM 節點alert(elem.files[0].name)} }export default App此時瀏覽器的顯示效果為:
在上面的代碼中,我們使用通過 ref 去獲取 DOM 節點,接著去獲取到文件的名字。像 file 這種類型的固定,值并不會一直固定的,所以也是一個非受控組件。
(4)總結梳理
setState 只能處理類似于前端的顯示和渲染相關的,像文件上傳這種交互類型的就處理不了。下面我們來梳理下非受控組件的幾大使用場景。具體如下:
- 必須手動操作 DOM 元素, setState 并無法手動操作 DOM 元素;
- 文件上傳類型 <input type=file> ;
- 某些富文本編輯器,需要傳入 DOM 元素。
受控組件 vs 非受控組件的區別如下:
- 優先使用受控組件,符合 React 設計原則;
- 必須操作 DOM 時,再使用非受控組件。
3、Protals
(1)為什么要用 Protals ?
一般情況下,組件默認會按照既定層次嵌套渲染。類似下面這樣:
<div id="root"><div><div><div class="model">Modal內容</div></div></div> </div>大家可以看到,這樣不斷嵌套,但里面卻只有一層區域的內容是有用的。從某種程度上來說,是非常不好的。那我們想做的事情是,如何讓組件渲染到父組件以外呢?
這個時候就需要用到 Protals 。
(2)如何使用
先來看一段代碼:
import React from 'react' import ReactDOM from 'react-dom' import './style.css'class App extends React.Component {constructor(props) {super(props)this.state = {}}render() {// 正常渲染// return <div className="modal">// {this.props.children} {/* vue slot */}// </div>// 使用 Portals 渲染到 body 上。// fixed 元素要放在 body 上,有更好的瀏覽器兼容性。return ReactDOM.createPortal(<div className="modal">{this.props.children}</div>,document.body // DOM 節點)} }export default Appstyle.css 代碼如下:
.modal {position: fixed;width: 300px;height: 100px;top: 100px;left: 50%;margin-left: -150px;background-color: #000;/* opacity: .2; */color: #fff;text-align: center; }此時,我們來看下瀏覽器節點的渲染效果。具體如下:
大家可以看到,通過使用 ReactDOM.createPortal() ,來創建 Portals 。最終 modals 節點成功脫離開父組件,并渲染到組件外部。
(3)使用場景
現在,我們來梳理一些 Protals 常見的場景。
protals 常用于解決一些 css 兼容性問題。通常使用場景有:
- overflow:hidden; 觸發 bfc ;
- 父組件 z-index 值太小;
- position:fixed 需要放在 body 第一層級。
4、context
(1)使用場景
有時候我們經常會有一些場景出現切換的頻率很頻繁,比如語言切換、或者是主題切換,那如何把對應的切換信息給有效地傳遞給每個組件呢?
使用 props ,又有點繁瑣;使用 redux ,又太小題大做了。
因此,這個需要我們可以用 react 中的 context 。
(2)舉例闡述
先來看一段代碼:
import React from 'react'// 創建 Context 填入默認值(任何一個 js 變量) const ThemeContext = React.createContext('light')// 底層組件 - 函數是組件 function ThemeLink (props) {// const theme = this.context // 會報錯。函數式組件沒有實例,即沒有 this// 函數式組件可以使用 Consumerreturn <ThemeContext.Consumer>{ value => <p>link's theme is {value}</p> }</ThemeContext.Consumer> }// 底層組件 - class 組件 class ThemedButton extends React.Component {// 指定 contextType 讀取當前的 theme context。// static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContextrender() {const theme = this.context // React 會往上找到最近的 theme Provider,然后使用它的值。return <div><p>button's theme is {theme}</p></div>} } ThemedButton.contextType = ThemeContext // 指定 contextType 讀取當前的 theme context。// 中間的組件再也不必指明往下傳遞 theme 了。 function Toolbar(props) {return (<div><ThemedButton /><ThemeLink /></div>) }class App extends React.Component {constructor(props) {super(props)this.state = {theme: 'light'}}render() {return <ThemeContext.Provider value={this.state.theme}><Toolbar /><hr/><button onClick={this.changeTheme}>change theme</button></ThemeContext.Provider>}changeTheme = () => {this.setState({theme: this.state.theme === 'light' ? 'dark' : 'light'})} }export default App此時瀏覽器的顯示效果為:
在上圖中,我們做到了主題的切換。現在,我們來分析下上述的代碼。
首先,我們創建了一個 Context ,也就是 ThemeContext ,并傳入了 light 值。
其次,核心在 <Toolbar /> 組件。 Toolbar 現有組件為 ThemedButton 和 ThemeLink 。其中,我們先指定 ThemedButton 的 contextType 去讀取當前的 ThemeContext ,那么就取到了默認值 light 。
接著,來到了 ThemeLink 組件。 ThemeLink 是一個函數式組件,因此,我們可以直接使用 ThemeContext.Consumer 來對其進行傳值。
上面兩個組件的值都取到了,但那只是 ThemeContext 的初始值。取到值了之后呢,我們還要修改值, React 會往上找到最近的 ThemeContext.Provider ,通過 value={this.state.theme} 這種方式,去修改和使用 ThemeContext 最終使用的值 。
5、異步組件(懶加載)
在項目開發時,我們總是會不可避免的去加載一些大組件,這個時候就需要用到異步加載。在 vue 中,我們通常使用 import() 來加載異步組件,但在 react 就不這么使用了。
React 通常使用 React.lazy 和 React.Suspense 來加載大組件。
如下代碼所示:
import React from 'react'const ContextDemo = React.lazy(() => import('./ContextDemo'))class App extends React.Component {constructor(props) {super(props)}render() {return <div><p>引入一個動態組件</p><hr /><React.Suspense fallback={<div>Loading...</div>}><ContextDemo/></React.Suspense></div>// 1. 強制刷新,可看到 loading (看不到就限制一下 chrome 的網速,Performance的network)// 2. 看 network 的 js 加載} }export default App首先我們使用 import 去導入我們要加載的組件。之后使用 React.lazy 去將這個組件給進行注冊,也就是 ContextDemo 。最后使用 React.Suspense 來加載 ContextDemo 。至此,我們完成了該異步組件的加載。
6、性能優化
(1)shouldComponentUpdate(簡稱SCU)
先來看下面這一段代碼:
shouldComponentUpdate(nextProps, nextState) {if (nextState.count !== this.state.count|| nextProps.text !== this.props.length) {return true // 可以渲染}return false // 不重復渲染 }在 React 中,默認的是,當父組件有更新,子組件也無條件更新。那如果每回都觸發更新,肯定不太好。
因此,這個時候我們需要用到 shouldComponentUpdate ,判斷當屬性有發生改變時,可以觸發渲染。當屬性不發生改變時,也就是前后兩次的值相同時,就不觸發渲染。
那這個時候我們 需要思考一個問題: SCU 一定要每次都用嗎?答案其實不是肯定的。
我們會去用 SCU ,從某種層面上來講就是為了優化。因此,我們需要依據當前的開發場景,有需要的時候再去優化。
現在,我們來總結一下 SCU 的使用方式,具體如下:
- SCU 默認返回 true ,即 React 默認重新渲染所有子組件;
- 必須配合 “不可變值” 一起使用;
- 可先不用 SCU ,有性能問題時再考慮使用。
(2)PureComponent和React.memo
PureComponent 在 react 中的使用形式如下:
class List extends React.PureComponent {constructor(props) {super(props)}render() {const { list } = this.propsreturn <ul>{list.map((item, index) => {return <li key={item.id}><span>{item.title}</span></li>})}</ul>}shouldComponentUpdate() {/*淺比較*/} }如果我們使用了 PureComponent ,那么 SCU 會進行淺層比較,也就是一層一層的比較下去。
下面我們來看 memo 。 memo ,顧名思義是備忘錄的意思。在 React 中的使用形式如下:
function MyComponent(props) {/* 使用props 渲染 */ }function areEqual(prevProps, nextProps) {/*如果把 nextProps傳入render方法的返回結果 與preProps傳入render方法的返回結果 一致的話,則返回true,否則返回false*/ } export default React.memo(MyComponent, areEqual);memo ,可以說是函數組件中的 PureComponent 。同時,使用 React.memo() 的形式,將我們的函數組件和 areEqual 的值進行比較,最后返回一個新的函數。
值得注意的是,在 React 中,淺比較已經使用于大部分情況,一般情況下,盡量不要做深度比較。
(3)不可變值
在 React 中,用于做不可變值的有一個庫: Immutable.js 。這個庫有以下幾大特點:
-
徹底擁抱“不可變值”
-
基于共享數據(不是深拷貝),速度好
-
有一定的學習和遷移成本,按需使用
下面來看一個使用例子:
const map1 = Immutable.Map({ a: 1, b: 2, c: 3 }) const map2 = map1.set('b', 50) map1.get('b') // 2 map2.get('b') // 50基本上現在在開發中都用這個庫來處理不可變值的問題。在實際使用中,可以看官方文檔按需使用即可。
7、關于組件公共邏輯的抽離
在 React 中,對于組件公共邏輯的抽離主要有三種方式要了解。具體如下:
- mixin ,已被 React 棄用
- 高階組件 HOC
- Render Props
下面將講解高階組件 HOC 和 Render Props 。
(1)高階組件 HOC
先看一段代碼:
// 高階組件不是一種功能,而是一種設計模式 // 1.傳入一個組件 Component const HOCFactory = (Component) => {class HOC extends React.Component {// 在此定義多個組件的公共邏輯render() {// 2.返回拼接的結果return <Component {this.props} /> }}return HOC } const EnhancedComponent1 = HOCFactory(WrappedComponent1) const EnhancedComponent2 = HOCFactory(WrappedComponent2)高階組件 HOC 是傳入一個組件,返回一個新的組件,見上方代碼的 1和2 。
下面來看一個例子,如下代碼所示:
import React from 'react'// 高階組件 const withMouse = (Component) => {class withMouseComponent extends React.Component {constructor(props) {super(props)this.state = { x: 0, y: 0 }}handleMouseMove = (event) => {this.setState({x: event.clientX,y: event.clientY})}render() {return (<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>{/* 1. 透傳所有 props 2. 增加 mouse 屬性 */}<Component {...this.props} mouse={this.state}/></div>)}}return withMouseComponent }const App = (props) => {const { x, y } = props.mouse // 接收 mouse 屬性return (<div style={{ height: '500px' }}><h1>The mouse position is ({x}, {y})</h1></div>) }export default withMouse(App) // 返回高階函數此時瀏覽器的顯示結果為:
在上面的代碼中,我們用 定義了高階組件 withMouse ,之后它通過 <Component {...this.props} mouse={this.state}/> 這種形式,將參數 props 和 props 的 mouse 屬性給透傳出來,供子組件 App 使用。
值得注意的是,在 react 中,還有一個比較常見的高階組件是 redux connect 。用一段代碼來演示:
import { connect } from 'react-redux';// connect 是高階組件 const VisibleTodoList = connect(mapStateToProps,mapDispatchToProps )(TodoList)export default VisibleTodoList現在,我們來看下 connect 的源碼,具體如下:
export const connect =(mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {class Connect extends Component {constructor() {super()this.state = {allProps: {}}}/* 中間省略 N 行代碼 */render () {return <WrappedComponent {...this.state.allProps} />}}return Connect }大家可以看到, Connect 也是同樣地,傳入一個組件,并返回一個組件。
(2)Render Props
先來看一段代碼:
// Render Props 的核心思想 // 1.通過一個函數,將class組件的state作為props,傳遞給純函數組件 class Factory extends React.Component {constructor() {tihs.state = {/* state 即多個組件的公共邏輯的數據 */}}/* 2.修改 state */render() {return <div>{this.props.render(this.state)}</div>} }const App = () => {// 3.在這里使用高階組件,同時將高階組件中的render屬性傳遞進來<Factory render={/* render 是一個函數組件 */(props) => <p>{props.a}{props.b} …</p>} /> }export default App;在上面的高階組件 HOC 中,最終返回的結果也是一個高階組件。但在 Render Props 中,我們把 Factory 包裹在定義的 App 組件中,最終再把 App 返回。
值得注意的是,在 Vue 中有類似于高階組件的用法,但沒有像 Render Props 類似的用法,這一點需要稍微留意一下。
下面來看一個例子,具體代碼如下:
import React from 'react' import PropTypes from 'prop-types'class Mouse extends React.Component {constructor(props) {super(props)this.state = { x: 0, y: 0 }}handleMouseMove = (event) => {this.setState({x: event.clientX,y: event.clientY})}render() {return (<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>{/* 將當前 state 作為 props ,傳遞給 render (render 是一個函數組件) */}{this.props.render(this.state)}</div>)} } Mouse.propTypes = {render: PropTypes.func.isRequired // 必須接收一個 render 屬性,而且是函數 }const App = (props) => (<div style={{ height: '500px' }}><Mouse render={/* render 是一個函數組件 */({ x, y }) => <h1>The mouse position is ({x}, {y})</h1>}/></div> )/*** 即,定義了 Mouse 組件,只有獲取 x y 的能力。* 至于 Mouse 組件如何渲染,App 說了算,通過 render prop 的方式告訴 Mouse 。*/export default App此時,瀏覽器的顯示效果如下:
在上面的代碼中,通過 this.props.render(this.state) 這種形式,將 Mouse 組件中的屬性傳遞給 App ,并讓 App 成功使用到 Mouse 的屬性值。
(3)HOC vs Render Props
現在,我們來梳理下 HOC 和 Render Props 的區別,具體如下:
- HOC:模式簡單,但會增加組件層級
- Render Props:代碼簡潔,學習成本較高
- 各有各的優缺點,根據實際場景按需使用即可
📚三、Redux和React-router
1、Redux
(1)Redux概念簡述
對于 react 來說,它是一個非視圖層的輕量級框架,如果要用它來傳遞數據的話,則要先父傳子,然后再慢慢地一層一層往上傳遞。
但如果用 redux 的話,假設我們想要某個組件的數據,那這個組件的數據則會通過 redux 來存放到 store 中進行管理。之后呢,通過 store ,再來將數據一步步地往下面的組件進行傳遞。
值得注意的是,我們可以視 Redux 為 Reducer 和 Flux 的結合。
(2)Redux的工作流程
Redux ,實際上就是一個數據層的框架,它把所有的數據都放在了 store 之中。我們先來看一張圖:
大家可以看到中間的 store ,它里面就存放著所有的數據。繼續看 store 向下的箭頭,然后呢,每個組件都要向 store 里面去拿數據。
我們用一個例子來梳理整張圖,具體如下:
- ①整張圖上有一個 store ,它存放著所有的數據,也就是存儲數據的公共區域;
- ②每個組件,都要從 store 里面拿數據;
- ③假設現在有一個場景,模擬我們要在圖書館里面借書。那么我們可以把 react Component 理解為借書人,之后呢,借書人要去找圖書館管理員才能借到這本書。而借書這個過程中數據的傳遞,就可以把它視為是 Action Creators ,可以理解為 “你想要借什么書” 這句話。
- ④ Action Creatures 去到 store 。這個時候我們把 store 當做是圖書館管理員,但是,圖書館管理員是沒有辦法記住所有圖書的數據情況的。一般來說,它都需要一個記錄本,你想要借什么樣的書,那么她就先查一下;又或者你想要還什么書,她也要查一下,需要放回什么位置上。
- ⑤這個時候就需要跟 reducers 去通信,我們可以把 reducers 視為是一個記錄本,圖書館管理員用這個記錄本來記錄需要的數據。管理員 store 通過 reducer 知道了應該給借書人 Components 什么樣的數據。
(2)react-redux
React-redux 中要了解的幾個點是 Provider 、 Connect 、 mapStateToProps 和 mapDisptchToProps 。
來看以下代碼:
import React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' import todoApp from './reducers' import App from './components/App'let store = createStore(todoApp)export default function() {return <Provider store={store}><App /></Provider> }react-redux 提供了 Provider 的能力,大家可以看到最后部分的代碼, Provider 將 <App /> 包裹起來,其實也就是說為它包裹的所有組件提供 store 能力,這也是 Provider 發揮的作用。
再來看一段代碼:
import { connect } from 'react-redux' import { toggleTodo } from '../actions' import TodoList from '../components/TodoList'// 不同類型的 todo 列表 const getVisibleTodos = (todos, filter) => {switch (filter) {case 'SHOW_ALL':return todoscase 'SHOW_COMPLETED':return todos.filter(t => t.completed)case 'SHOW_ACTIVE':return todos.filter(t => !t.completed)} }const mapStateToProps = state => {// state 即 vuex 的總狀態,在 reducer/index.js 中定義return {// 根據完成狀態,篩選數據todos: getVisibleTodos(state.todos, state.visibilityFilter)} }const mapDispatchToProps = dispatch => {return {// 切換完成狀態onTodoClick: id => {dispatch(toggleTodo(id))}} }// connect 高階組件,將 state 和 dispatch 注入到組件 props 中 const VisibleTodoList = connect(mapStateToProps,mapDispatchToProps )(TodoList)export default VisibleTodoList在上面的代碼中, connect 將 state 和 dispatch 給注入到組件的 props 中,將屬性傳遞給到 TodoList 組件。
(3)異步action
redux 中的同步 action 如下代碼所示:
// 同步 action export const addTodo = text => {// 返回 action 對象return {type: 'ADD_TODO',id: nextTodoId++,text} }redux 中的異步 action 如下代碼所示:
// 異步 action export const addTodoAsync = text => {// 返回函數,其中有 dispatch 參數return (dispatch) => {// ajax 異步獲取數據fetch(url).thne(res => {// 執行異步 actiondispatch(addTodo(res.text))})} }(4)Redux數據流圖
Redux 的單項數據流圖如下所示:
關于 Redux 更詳細內容,可查看這篇文章:Redux從入門到進階,看這一篇就夠了!
2、React-router
(1)路由模式
React-router 和 vue-router 一樣,都是兩種模式。具體如下:
- hash 模式(默認),如 http://abc.com/#/user/10
- H5 history 模式,如 http://abc.com/user/20
- 后者需要 server 端支持,因此無特殊需求可選擇前者
hash模式的路由配置如下代碼所示:
import React from 'react' import {HashRouter as Router,Switch,Route } from 'react-router-dom'function RouterComponent() {return(<Router><Switch><Route exact path="/"><Home /></Route><Route exact path="/project/:id"><Project /></Route><Route path="*"><NotFound /></Route></Switch></Router>) }History模式的路由配置如下:
import React from 'react' import {BrowserRouter as Router,Switch,Route } from 'react-router-dom'function RouterComponent() {return(<Router><Switch><Route exact path="/"><Home /></Route><Route exact path="/project/:id"><Project /></Route><Route path="*"><NotFound /></Route></Switch></Router>) }注意,hash 和 history 的區別在于 import 中的 HashRouter 和 BrowserRouter 。
關于 hash 和 history 相關的內容,進一步了解可查看這篇文章:淺談前端路由原理hash和history
(2)路由配置
Ⅰ. 動態路由
假設現在有父組件 RouterComponent ,具體代碼如下:
function RouterComponent() {return(<Router><Switch><Route exact path="/"><Home /></Route><Route exact path="/project/:id"><Project /></Route><Route path="*"><NotFound /></Route></Switch></Router>) }其中,在這個組件中還有一個 Project 組件,需要進行動態傳參。
繼續,我們來看下子組件 Project 組件時如何進行動態傳參的。具體代碼如下:
import React from 'react' import { Link, useParams } from 'react-router-dom'function Project() {// 獲取 url 參數,如 '/project/100'const { id } = useParams()console.log('url param id', id)return (<div><Link to="/">首頁</Link></div>) }大家可以看到,在 React 中,通過 const { id } = useParams() 這樣的形式,來進行動態傳參。
還有另外一種情況是跳轉路由。請看以下代碼:
import React from 'react' import { useHistory } from 'react-router-dom'function Trash() {let history = useHistory()function handleClick() {history.push('/')}return (<div><Button type="primary" onClick={handleClick}>回到首頁</Button></div>) }大家可以看到,通過使用 useHistory ,讓點擊事件跳轉到首頁中。
Ⅱ. 懶加載
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import React, { Suspence, lazy } from 'react';const Home = lazy(() => import('./routes/Home')); const About lazy(() => import('./routes/About'));const App = () => {<Router><Suspense fallback={<div>Loading……</div>}><Switch><Route exact path="/" component={Home}/><Route path="/about" component={About}/></Switch></Suspense> </Router> }在 React 中,我們可以直接用 lazy() 包裹,對頁面的內容進行懶加載。當然,還有另外一種情況是,加載類似于首頁初次加載頁面 Loading 的那種效果,在 react 中可以使用 <Suspense> 來解決。
🗞?四、結束語
在上面的文章中,我們講解了 react 的基本使用以及高級特性。同時,還講解了 react 的周邊插件, Redux 和 React-router 。
前端在做 react 的項目時,總是脫離不開以上文章所涉及到的知識點,唯一的區別在于基本使用的內容用的較多,而高級特性的使用場景相對會少一些。
希望通過上文的講解,小伙伴們有所收獲🥂
- 關注公眾號 星期一研究室 ,第一時間關注學習干貨,更多 「offer來了」 面試專欄待你解鎖~
- 如果這篇文章對你有用,記得留個腳印jio再走喲~
- 我們下期見!🔑🔑🔑
總結
以上是生活随笔為你收集整理的探秘react,一文弄懂react的基本使用和高级特性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iphone怎么安装ipa文件?
- 下一篇: 使用Microsoft Word2016