寫這篇文章初衷是整理一下本身這近幾個月的心路歷程,從剛開始(19年10月份) 「入坑」
react
時的一臉懵,不知如何下手,到如今能夠寫簡單的業務。講述本身從徹底不瞭解這個框架 ,而後又一步步上道兒,我這裏就稱之爲「爬坑」,哈哈。因此想把本身的學習過程以及爬坑記錄下來,給本身往後翻閱,若有也在寫react
,想交流的小夥伴,文末有微信哦,哈哈。其實從很早就想研究下react
了,只是時間上的不容許,如今終於能夠騰出時間來(其實平時工做較飽和,也只能擠業餘時間了)。html
------文中的示例都是本身通過實踐的,如理解有誤,還請告知哦!😂------vue
項目使用
umi
腳手架配合dva
搭建,ui
組件是ant-design
,模版使用的是antdesign的pro-layout
現成的模版,較多使用aHooks
來實現網絡請求、節流等操做,是一個使用了都說真香的Hooks
庫,參考個人另外一篇文章:aHooksreact
具體版本號以下:ios
"@ant-design/pro-layout": "4.7.0", "@antv/g2": "^3.5.11", "antd": "^3.25.2", "array-move": "^2.2.0", "umi": "2.12.3", "ahooks": "^2.0.1", "umi-plugin-react": "1.14.7", "uuid": "^3.3.3", "axios": "^0.19.0", "bizcharts": "^3.5.6", "classnames": "^2.2.6", "copy-to-clipboard": "^3.2.0", "dayjs": "^1.8.17", "immutable": "^4.0.0-rc.12", "lodash": "^4.17.15", "moment": "^2.24.0", "mz-modules": "^2.1.0", "parameter": "^3.6.0", "prop-types": "^15.7.2", "qrcode": "^1.4.4", "qs": "^6.9.1", "rc-form-hooks": "^0.0.1-alpha.22", "react": "^16.12.0", "react-dom": "^16.12.0", "swr": "^0.1.12", 複製代碼
react
的生命週期如同
vue
同樣,react
也是有本身的生命週期,方便咱們根據加載順序來執行相應的操做。但因爲Hooks
的出現,彷佛react
已經不在須要關心這些生命週期的問題,Hooks
是React 16.8
新增的特性,在你不須要寫class
組件的狀況下,就賦予了函數式組件state
狀態管理及生命週期函數的特性,固然下面也會詳細介紹hooks
將如何使用。json
經常使用的生命週期以下:axios
在渲染前調用:
componentWillMount
api在第一次渲染後調用:
componentDidMount
數組在組件完成更新前調用:
componentWillUpdate
瀏覽器在組件完成更新後當即調用:
componentDidUpdate
緩存在組件接收到一個新的 prop (更新後)時被調用:
componentWillReceiveProps
在組件從 DOM 中移除以前馬上被調用:
componentWillUnmount
加載渲染過程:
父 componentWillMount => 父 render => 子 componentWillMount =>子 render => 子 componentDidMount => 父componentDidMount
複製代碼
子組件經過props取值並更新過程:
子 componentWillUpdate => 父 render => 子 componentWillReceiveProps => 父 componentWillUpdate => 子 render => 子 componentDidUpdate => 父 componentDidUpdate
複製代碼
單一父 / 子組件(不依賴props)更新過程:
componentWillUpdate => render => componentDidUpdate
複製代碼
銷燬過程:
componentWillUnmount
複製代碼
let root = document.getElementById('example'); /* * 相似vue的template ,第一個參數是插入的模版,第二個是插入的根元素 * 在react中樣式中的 class 要重命名爲 className * render 是必不可少的,用來渲染到頁面上 */ ReactDOM.render( <h1 className="box">Hello, world!</h1>, root ); 複製代碼
效果展現:
在react中定義變量是很方便的,能夠定義數組,數組中能夠包含咱們的
html
標籤,而後能夠將變量直接帶入到頁面上。
<body> <div id="example"></div> <script type="text/babel"> let root = document.getElementById('example') let arr = [ <h1 >Hello world!</h1>, <h2 >Hello React!</h2>, ]; // 注意,react 的變量使用的是單花括號 {} ReactDOM.render( <div>{arr}</div>, root ); </script> 複製代碼
效果展現:
在
React
裏組件起初都是用class
來寫的(雖然如今都在用hooks
,但這部分仍是保留,就當是記錄下它的發展史,hooks
來寫組件無疑是很爽的,下面會介紹到。)。
<body> <div id="example"></div> <script type="text/babel"> let root = document.getElementById('example'); // class 的名字必須大寫,並繼承自 React.Component class HelloMessage extends React.Component { constructor(...args){ super(...args); this.name=this.props.name this.job=this.props.job this.age = this.props.age } fn(){ return "Aaa" } render() { // 變量還能夠直接定義標籤,style後面需跟一個{},而裏面的內容須要是一個json,因此此處看起來是兩個{{}} let div =<div style={{color:'red'}}>我是div</div> return ( <div> // 花括號中的值還能夠參與計算 姓名: {this.name}<br/> 工做: {this.job}<br/> 年齡: {this.age+3}<br/> // 花括號不只能夠輸出變量,還能夠輸出方法 {this.fn()} <br/> // 將標籤輸出到頁面 {div} </div> ); } } ReactDOM.render( <HelloMessage name="John" job="teacher" age="18"/>, root ); </script> </body> 複製代碼
JSX
語法容許咱們把html
和js
穿插來寫,咱們來看看最經常使用的循環怎麼寫。
<body> <div id="example"></div> <script type="text/babel"> let root = document.getElementById('example'); class HelloMessage extends React.Component { constructor(...args){ super(...args) } render() { let root = document.getElementById('example') let names = ['Alice', 'Emily', 'Kate']; return ( <div> names.map(function (item) { // 循環中須要添加key值,用來保證惟一性 return <div key={item}>Hello, {item}!</div> }) </div> ); } } ReactDOM.render( <div> <HelloMessage /> </div>, root ); </script> </body> 複製代碼
效果展現:
咱們日常的開發中,有時候須要用到些公共組件,那咱們就應對其進行封裝提取出來,如下是粗略版的父子組件嵌套寫法
<body> <div id="example"></div> <script type="text/babel"> // 父組件 class Parent extends React.Component{ constructor(...args){ super(...args) } render(){ return( <ul> // 將寫好的子組件嵌套進來便可 <Child/> <Child/> <Child/> <Child/> </ul> ) } } // 子組件 class Child extends React.Component{ constructor(...args){ super(...args) } render(){ return( <li>111</li> ) } } ReactDOM.render( <Parent />, document.getElementById('example') ); </script> </body> 複製代碼
通常狀況下,render 裏面只會有一個最大的標籤包含,若是你有兩個標籤,請在外面添加一個包裹標籤,正確寫法:
ReactDOM.render( <div> <Parent></Parent> <Child></Child> </div>, document.getElementById('example') ); 複製代碼
錯誤寫法:
ReactDOM.render( <Child></Child> <Parent></Parent> , document.getElementById('example') ); 複製代碼
在腳手架中還可用
react
中的空標籤<></>
去充當咱們最外層的包裹層,它的好處是不會多生成一個div
import { Component } from 'react' class ConfigContent extends Component { constructor(...args){ super(...args) } render(){ return( <> <Parent></Parent> <Child></Child> </> ) } } export default ConfigContent 複製代碼
組件能夠寫成單標籤或者雙標籤兩種形式。以下:
// 雙標籤
<Parent></Parent>
// 單標籤
<Parent/>
複製代碼
props
父組件給子組件傳遞參數,子組件接收,並渲染子組件: 父組件=>子組件
父組件
import { PureComponent } from "react"; import Child from "./child"; class Parent extends PureComponent { constructor(props) { super(props); this.state = { id: 1 }; } render() { return ( <Child id={this.state.id} /> ); } } export default Parent; 複製代碼
子組件:
import { PureComponent } from "react"; class Child extends PureComponent { constructor(props) { super(props); } render() { return ( <div> <h1>child-page</h1> <p>{this.props.id}</p> </div> ); } } export default Child; 複製代碼
效果展現:
子組件經過事件將子組件的值傳到父組件: 子組件=>父組件
子組件:
import { Button } from "antd"; import { PureComponent } from "react"; class Child extends PureComponent { constructor(props) { super(props); this.state = { a: 1 }; } action = { handleChange: () => { this.props.changeEvent(`子組件定義的值:${this.state.a}`); } }; render() { return ( <div> <h1>child-page</h1> <Button type="primary" onClick={this.action.handleChange}> 改變父組件 </Button> </div> ); } } export default Child; 複製代碼
父組件:
import { PureComponent } from "react"; import { Button } from "antd"; import Child from "./child"; class Parent extends PureComponent { constructor(props) { super(props); } action = { changeEvent: mode => { console.log("父組件收到的值:", mode); } }; render() { return ( <div> <Child changeEvent={mode => this.action.changeEvent(mode)} /> </div> ); } } export default Parent; 複製代碼
點擊後的效果展現:
事件是咱們在交互過程當中必不可少的,那麼咱們試試看,
在react
中如何添加事件。
(1)咱們原生的添加事件的方式,採用的是小寫onclick
:
<button onclick="activateLasers()"> Activate Lasers </button> 複製代碼
(2)react的添加事件,採用駝峯的方式定義onClick
:
<button onClick={activateLasers}>
Activate Lasers
</button>
複製代碼
下面介紹幾種在項目中添加事件的方式,你們可根據狀況選擇:
<body> <div id="example"></div> <script type="text/babel"> class Child extends React.Component { constructor(...args){ super(...args) this.a=[123] } // 直接在標籤上添加 render() { return ( <div onClick={function(){ console.log("eee") }}>{this.a}</div> ) } } ReactDOM.render( <Child/>, document.getElementById('example') ) </script> </body> 複製代碼
class
組件中添加方法(須要從新綁定this):<body> <div id="example"></div> <script type="text/babel"> class Cmp1 extends React.Component{ constructor(...args){ super(...args) } fn(){ // props只讀的,這裏的值是不可改的,是從外面傳進來的 console.log(this.props.a) // 0 } // onClick 相似原生事件,此處bind就是把咱們的fn的this緊緊綁在組件上,此時的內部也能拿到咱們組件的this render(){ return( <div> {this.props.a} <input type="button" value="+1" onClick={this.fn.bind(this)}/> </div> ) } } ReactDOM.render( <div> <Cmp1 a={0}/> </div>, document.getElementById('example') ); </script> </body> 複製代碼
<body> <div id="example"></div> <script type="text/babel"> class Child extends React.Component { constructor(...args){ super(...args) this.a=[123] } // 定義變量 action ={ fn(){ console.log(23) } } // 在這裏直接調用,就不用綁定this了 render() { return ( <div onClick={this.action.fn}>{this.a}</div> ) } } ReactDOM.render( <Child/>, document.getElementById('example') ) </script> </body> 複製代碼
<body> <div id="example"></div> <script type="text/babel"> class Child extends React.Component { constructor(...args){ super(...args) this.a=[123] } fn=()=>{ console.log(23) } render() { return ( <div onClick={this.fn}>{this.a}</div> ) } } ReactDOM.render( <Child/>, document.getElementById('example') ) </script> </body> 複製代碼
當咱們瞭解到
React Hooks
的事件添加後,咱們終於鬆了口氣,終於再也不考慮this
綁定的問題了
import React from 'react'; import { Button } from 'antd'; const App = () => { const handleClick = () => { console.log('按鈕點擊'); }; return <Button onClick={handleClick}>按鈕</Button>; }; export default App; 複製代碼
備註一下,關於事件傳參的寫法:
import { PureComponent } from "react"; import { Button } from "antd"; class App extends PureComponent { constructor(props) { super(props); this.state = { arr: [1, 2, 3, 4] }; } action = { handleClick: i => { console.log(i.target.innerHTML); } }; render() { return ( <div> {this.state.arr.map((item, index) => { return ( <Button key={index} onClick={index => this.action.handleClick(index)} > {item} </Button> ); })} </div> ); } } export default App; 複製代碼
效果展現:
state
製做一個 input ++ 功能
state
構造函數是惟一可以初始化this.state
的地方,接收一個對象,是可變的,能夠是內部加的,也能夠從外部傳進來,this.setSate
是惟一改變state
的方式。
// 製做一個input ++ 功能 <body> <div id="example"></div> <script type="text/babel"> class Cmp1 extends React.Component{ constructor(...args){ super(...args) this.state={a:0} } fn(){ // this.setSate是惟一改變state的方式 this.setState({a:this.state.a+1}) } render(){ return( <div> {this.state.a} <input type="button" value="+1" onClick={this.fn.bind(this)}/> </div> ) } } ReactDOM.render( <div> <Cmp1/> </div>, document.getElementById('example') ); </script> </body> 複製代碼
頁面效果:
咱們在跳轉路由時,有時須要給跳轉到到頁面攜帶一些參數來定位頁面的顯示或回顯數據,此小節咱們就來看看如何傳參,以及入股取參。
首先咱們先拿一下props,看看都有哪些參數:
console.log(this.props);
複製代碼
參數以下:
咱們來具體解析一下:
history:包含了路由push replace goBack 等方法,以及可拿到query state 等參數
history:{ location:{ pathname:'/dashboard/workplace', // url地址 search:'?name='xiaoxiao', // 拿到的是完整的參數字符串 hash:'', query:{name:'xiaoxiao'}, // 拿到參數的對象格式 state:undefined // 拿到經過state傳入的參數 }, push:function push(path,state){} , // 跳轉到指定路徑 replace:function replace(path,state){} , // 跳轉到指定路徑,不會保留history goBack:function goBack(path,state){} , // 返回上一個路由地址 } 複製代碼
這個location 同上面的location,可拿到query參數 以及state 參數
location:{ pathname:'/dashboard/workplace', search:'?name='xiaoxiao', query:{name:'xiaoxiao'}, state:undefined } 複製代碼
包含了具體的 url 信息,並能夠拿到params的參數的值。
match:{ path:'/dashboard/workplace', url:'/dashboard/workplace', params:{} } 複製代碼
接下來咱們就使用:this.props.history.push
來模擬跳轉,並攜帶參數:
經過
url
來進行傳參,地址欄是可見的
⚠️注意️:刷新頁面後,參數不會丟失,可傳對象
A 頁面:
this.props.history.push({ pathname: "/dashboard/workplace", query: { name: "xiaoqiu" } }); 複製代碼
B頁面 (參數在url
裏問號後顯示)
http://localhost:8000/#/dashboard/workplace?name=xiaoqiu 複製代碼
打印一下咱們接收到的參數:
state傳參,同query差很少,只是屬性不同,並且state傳的參數是加密的,不會在地址欄顯示
注意️:刷新頁面後,參數就會丟失,可傳對象
A頁面:
this.props.history.push({ pathname: "/dashboard/workplace", state: { name: "xiaoqiu" } }); 複製代碼
B頁面: (參數並無出如今地址欄哦!)
http://localhost:8000/#/dashboard/workplace 複製代碼
打印一下咱們接收到的參數:
此時咱們刷新一下頁面,看看是否還能夠拿到state
的值:
這時
state
的值已經丟失,評論中有位掘友反應,刷新後在history.state
裏面有state
的值,我想說,history
沒有直接的state
屬性,state
是在history
的location
的屬性中,不知道是否能解釋那位掘友的問題🤔,歡迎你們一塊兒討論哦。
追加:獲得了掘友新的反饋:刷新頁面state的值會存在瀏覽器的history.state中,接下來咱們作一下驗證: 首先在接到參數頁面打印:
history.state
複製代碼
通過上面的查詢,拿到的結果是null,我去查閱了相關api
history.pushState({page: 1}, "title 1", "?page=1") 複製代碼
使用pushState賦值的能夠拿到history.state:
search
同query
參數同樣是經過url
進行傳輸
⚠️注意️:刷新頁面後,參數不會丟失,但只可傳字符串,不能傳輸對象
A頁面:
this.props.history.push({ pathname: "/dashboard/workplace", search: "a=1&b=2" }); 複製代碼
B頁面
http://localhost:8000/#/dashboard/workplace?a=1&b=2 複製代碼
打印一下咱們接收到的參數:
此時咱們注意到不管是經過
query
或是search
傳參,返回參數時二者也同時都能取到,只是展示方式不一樣。query
是以對象形式展示,search
是以字符串展示,若是你想在地址欄顯示,那就用query
,若是想加密傳輸,那就用state
,但要注意使用state
傳遞參數刷新後就會丟失哦!
ref
獲取組件實例經過給組件綁定ref 能夠獲取到整個子組件的實例,進而可傳參數並調用其方法
ref
,獲取當前組件的參數以及事件import { Button } from "antd"; import { PureComponent } from "react"; class Child extends PureComponent { constructor(props) { super(props); this.state = { a: 1 }; } action = { handleChange: () => { console.log(this.refs["button"]); } }; render() { return ( <div> <h1>child-page</h1> <Button type="primary" onClick={this.action.handleChange} ref="button"> 改變父組件按鈕 </Button> </div> ); } } export default Child; 複製代碼
控制檯打印ref
拿到的組件參數以及方法:
react
的createRef
,拿到子組件的參數和方法import { PureComponent, createRef } from "react"; import { Button } from "antd"; import Child from "./child"; class Parent extends PureComponent { constructor(props) { super(props); this.children = createRef(); this.state = { id: 1, arr: [1, 2, 3, 4] }; } action = { handleClick: () => { console.log(this.children); } }; render() { return ( <div> <Button onClick={() => this.action.handleClick()}> 按鈕 </Button> 子組件: <Child ref={this.children} /> </div> ); } } export default Parent; 複製代碼
控制檯輸出子組件的值:
父組件-函數組件:
import { useRef, useEffect } from 'react' import Child from './testChild' const Parent = () => { const divRef = useRef() useEffect(() => { console.log('divRef', divRef.current) },[]) return ( <div> hello hooks <Child ref={divRef} text={'子組件'} /> </div> ) } export default Parent 複製代碼
子組件-class組件:
import { PureComponent } from 'react' class Child extends PureComponent { constructor(props) { super(props) this.state = { count: 12 } } render() { return <div>子組件</div> } } export default Child 複製代碼
控制檯輸出:
⚠️ 注意: ️若是想拿到子組件的實例,子組件必須是一個class類組件:參考react官網
createContext
: 跨越子組件,實現祖孫數據傳輸咱們在日常的開發中,若是遇到孫組件須要使用到爺爺組件的數據,咱們通常會通過中間的父組件,一層一層向下傳播,但有時中間的父組件不須要用到這些給孫子組件的值,再或者中間父組件層級過多,或者邏輯業務變更,那就須要咱們從頭至尾進行一系列的修改,有點得不償失,今天咱們就來實現一個從爺爺組件,跨越父組件,直通孫子組件的方法:
Provider
顧名思義,參數的提供者,將須要傳遞的參數經過它來暴露出來 Consumer
參數的接收者,用在須要接收參數的組件
首先將引入以及建立context,並拋出 Provider、Consumer
/** * 建立 context 並導出Provider、Consumer * Provider:通常用於提供參數的組件 * Consumer:通常用在接受參數的組件 * */ import React from 'react' let { Provider, Consumer } = React.createContext() export { Provider, Consumer } 複製代碼
導入Provider
:父/祖父組件,要位於Consumer
的上層組件:
import { Provider } from '@@/utils/context' class Parent extends PureComponent { constructor(props) { super(props) this.state = { name: 'zhangsan' } } render() { return ( <Provider value={this.state.name}> <div> <p>父組件定義的值:{name}</p> <Son></Son> </div> </Provider> ) } } export default Parent 複製代碼
拋出:Consumer
子/孫組件,要位於Provider
的下層的組件,首先測試第一層的兒子組件:
import { Consumer } from '@@/utils/context' class Son extends PureComponent { constructor(props) { super(props) } render() { return ( <Consumer> { name=>( <div> <p>子組件接收的值:{name}</p> <Grandson></Grandson> </div> ) } </Consumer> ) } } export default Son 複製代碼
接下來測試第N層的孫子組件,寫法同上:
import { Consumer } from '@@/utils/context' class Grandson extends PureComponent { constructor(props) { super(props) } render() { return ( <Consumer> { name=>( <div> <p>孫子組件接收的值:{name}</p> </div> ) } </Consumer> ) } } export default Grandson 複製代碼
當多個參數須要傳遞時,就直接放在一個對象({})裏就能夠,子組件收到的也時一個對象
父組件:
import React from 'react'; import { Provider } from '@xb/utils/context'; import Child from './Child1'; const App = () => { const state = false; const params = { name: 'zhangsan', age: 18, job: 'teacher', }; return ( <Provider value={{ params, state }}> 子組件:<Child /> </Provider> ); }; export default App; 複製代碼
Child:
import React from 'react'; import { Consumer } from '@xb/utils/context'; const Child1 = () => { return ( <Consumer> {data => ( <div> <p> 子組件接收的值:{data.params.age}--{data.state} </p> </div> )} </Consumer> ); }; export default Child1; 複製代碼
總結:從上層往下傳值,剛剛咱們實驗的是字符串,對象,其實還可傳輸數組。基本知足上層無需層層傳遞,下層也可拿到上層的數據。當咱們的項目須要在多處使用context
,那也時沒有問題的,每一個Consumer
只會去找它的上級傳來的值,而不會去兄弟組件尋找。
React.Hooks
函數式組件實踐上面劇透了不少次,這一講來說一下使人心動💓 的
hooks
。那麼什麼是hooks
?
借用官網的解釋:
Hook
是React
16.8 的新增特性。它可讓你在不編寫class
的狀況下使用state
以及其餘的React
特性。
import React, { useState } from 'react'; function Example() { // 聲明一個新的叫作 「count」 的 state 變量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } 複製代碼
useState
: 存儲state
,更新state
咱們都知道
hooks
的一大亮點就是在不編寫class
的狀況下使用state
,那麼hooks
的state
和class
的state
的區別是什麼呢?該怎麼使用呢?
定義state
const [state, setState] = useState(initialState); 複製代碼
state
,以及更新 state
的函數更新state
setState(newState); 複製代碼
在初始渲染期間,返回的狀態 (
state
) 與傳入的第一個參數 (initialState
) 值相同。setState
函數用於更新state
。它接收一個新的state
值並將組件的一次從新渲染加入隊列。
下面上一個簡單數字 + +的例子:
import React, { useState } from 'react'; import { Button, Card } from 'antd'; const App = () => { const [state, setState] = useState<number>(0); return ( <Card> <p>count: {state}</p> <Button type="primary" onClick={() => { setState(state + 1); }} > + </Button> </Card> ); }; export default App; 複製代碼
useEffect
: 監聽參數變化,執行的函數
useEffect
不管是否有參數的監聽,React
都會等待瀏覽器完成畫面渲染以後纔會延遲調用 。若是你不想讓它首次就執行,能夠考慮使用:useUpdateEffect -- 它是一個只在依賴更新時執行的useEffect hook
useEffect
會在每輪組件渲染完成後執行。這樣的話,一旦 useEffect
的依賴發生變化,它就會被從新建立。useEffect(() => {
handleSubmit();
}, []);
複製代碼
tabkey
發生變化後,會自動執行函數體內的handleSubmit
方法useEffect(() => {
handleSubmit();
}, [tabKey]);
複製代碼
tabKey
或者state
改變都會執行函數體內的handleSubmit
方法:useEffect(() => {
handleSubmit();
}, [tabKey,state]);
複製代碼
useEffect
裏 return
出 一個函數,這樣return
的函數體內的內容會在銷燬時執行:useEffect(() => { handelColumns(tabVal || 'intention'); // 須要是一個函數哦!!! return () => { // 如下內容只會在銷燬的時候執行 console.log(1); }; }, []); 複製代碼
將來可期:依賴項數組不會做爲參數傳給
effect
函數。雖然從概念上來講它表現爲:全部effect
函數中引用的值都應該出如今依賴項數組中。將來編譯器會更加智能,屆時自動建立數組將成爲可能。
useReducer
: useState
的替代方案,存儲state
,更新state
useReducer
做爲useState
的替代方案。在某些場景下,useReducer
會比useState
更適用,例如:state
邏輯較複雜且包含多個子值,或者下一個state
依賴於以前的state
等。
接收參數:
(state, action) => newState
的 reducer
函數;state
的初始值;返回值:
useReducer
返回一個數組,數組中包含一個 state
和 dispath
,state
是返回狀態中的值,而 dispatch
是一個能夠發佈事件來更新 state
的函數。有兩種不一樣初始化
useReducer
state
的方式,你能夠根據使用場景選擇其中的一種。將初始state
做爲第二個參數傳入useReducer
是最簡單的方法,下面咱們使用【 input ++、--】的例子來看一下它如何使用:
第一種:指定初始化,將初始
state
做爲第二個參數傳入
import React, { useReducer } from 'react'; import { Button } from 'antd'; import CardLayout from '@xb/layouts/CardLayout'; const initialState = { count: 0 }; const reducer = (state, action) => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } }; const IntendedList = () => { const [state, dispatch] = useReducer(reducer, initialState); return ( <CardLayout> <p>count: {state.count}</p> <Button type="primary" onClick={() => { dispatch({ type: 'increment' }); }} style={{ marginRight: 30 }} > + </Button> <Button type="primary" onClick={() => { dispatch({ type: 'decrement' }); }} > - </Button> </CardLayout> ); }; export default IntendedList; 複製代碼
第二種:惰性初始化,你能夠選擇惰性地建立初始
state
。爲此,須要將init
函數做爲useReducer
的第三個參數傳入,這樣初始state
將被設置爲init(initialArg)
。
import React, { useReducer } from 'react'; import { Button } from 'antd'; import CardLayout from '@xb/layouts/CardLayout'; const initialCount = 0; const init = initialCount => { return { count: initialCount }; }; const reducer = (state, action) => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; case 'reset': return init(action.payload); default: throw new Error(); } }; const IntendedList = () => { const [state, dispatch] = useReducer(reducer, initialCount, init); return ( <CardLayout> <p>count: {state.count}</p> <Button type="primary" onClick={() => { dispatch({ type: 'reset', payload: initialCount }); }} style={{ marginRight: 30 }} > reset </Button> <Button type="primary" onClick={() => { dispatch({ type: 'increment' }); }} style={{ marginRight: 30 }} > + </Button> <Button type="primary" onClick={() => { dispatch({ type: 'decrement' }); }} > - </Button> </CardLayout> ); }; export default IntendedList; 複製代碼
頁面的展示(gif
圖沒作成,先看這個把😄 ):
useCallback
緩存回調函數,僅在某個依賴項改變時纔會更新相似
useEffect
,把內聯回調函數及依賴項數組做爲參數傳入useCallback
,它將返回該回調函數的memoized
版本,該回調函數僅在某個依賴項改變時纔會更新。當你把回調函數傳遞給通過優化的並使用引用相等性去避免非必要渲染(例如shouldComponentUpdate
)的子組件時,它將很是有用。
參數:
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], ); 複製代碼
useMemo
: 緩存變量,僅在某個依賴項改變時纔會更新把建立函數和依賴項數組做爲參數傳入
useMemo
,它僅會在某個依賴項改變時才從新計算memoized
值。這種優化有助於避免在每次渲染時都進行高開銷的計算。
const compute = useMemo(() => { const count = 222; // 這裏使用 a或b 針對 count 作一些很複雜的計算 // 只有當依賴的 a 或者 b 的值發生改變時纔會從新計算,不然返回的是以前緩存的值 return count * a * b; }, [a, b]); 複製代碼
hook
有些知識真的是須要必定的積累後,才能理解,而且要多多學而時習之,就好比這個自定義
hook
,如今終於能模仿着寫出一些小例子了,分享給你們。
首先介紹一下,自定義 Hooks
容許建立自定義 Hook
,只要函數名遵循以 use
開頭,且返回非 JSX
元素,就是 Hooks
啦!自定義 Hooks
內還能夠調用包括內置 Hooks
在內的全部自定義 Hooks
。
這是一個經過監聽參數值來判斷當前的登陸狀態的hook
,先使用 手動的 + 、-
來模擬一個動態監聽狀態,當數值大於0爲在線狀態,反之爲離線狀態,下面是代碼:
hook
頁面:
import { useState, useEffect } from 'react'; /** * @param value {number} * @return {boolean} */ const useOnlineState = (value: number): boolean => { const [state, setState] = useState(false); useEffect(() => { if (value > 0 && Boolean(value)) { setState(true); return; } setState(false); }, [value]); return state; }; export default useOnlineState; 複製代碼
調用頁面:
import React, { useState } from 'react'; import { Button, Card } from 'antd'; import useOnlineState from './test'; const App = () => { const [count, setCount] = useState(0); const onlineState = useOnlineState(count); return ( <Card> <p> {count}</p> <Button type="primary" onClick={() => { setCount(count + 1); }} > + </Button> <Button style={{ margin: '0px 0px 20px 30px' }} type="primary" onClick={() => { setCount(count - 1); }} > - </Button> <p>當前狀態{onlineState ? '在線' : '離線'}( 數值大於0爲在線狀態,反之爲離線狀態 )</p> </Card> ); }; export default App; 複製代碼
頁面展示:
上面咱們使用自定義組件導出了一個online
狀態,咱們還可使用導出函數方法,以下導出狀態和顯示隱藏的方法:
Hook:
import { useCallback, useState } from 'react'; export type IHook = [ boolean, { hide: () => void; show: () => void; }, ]; /** * 分享 * @param {string} value * @return {IHook} */ const useShowinfo = (value: boolean): IHook => { const [state, setState] = useState<boolean>(value); const hide = useCallback(() => { setState(false); }, []); const show = useCallback(() => { setState(true); }, []); return [state, { hide, show }]; }; export default useShowinfo; 複製代碼
調用頁面:
import React from 'react'; import { Button, Card } from 'antd'; import useShowinfo from './test'; const App = () => { const [state, { show, hide }] = useShowinfo(false); return ( <Card> <Button type="primary" onClick={() => { show(); }} > show </Button> <Button style={{ margin: '0px 0px 20px 30px' }} type="primary" onClick={() => { hide(); }} > hide </Button> <p>{state ? 'show' : 'hide'}</p> </Card> ); }; export default App; 複製代碼
頁面展示:
React.memo
當子組件依賴的props
未發生改變,緩存子組件,不進行渲染咱們日常開發項目時,通常狀況下都本着一個模塊負責一起業務,獲取數據和邏輯通常放在父組件,渲染放在子組件。那麼當咱們的父組件頁面數據發生變化後,不管是否傳給子組件
props
,子組件就會從新渲染,固然這並非咱們想要的結果,咱們期待的結果:只有當子組件依賴父組件當props
發生變化後,再次渲染子組件,下面就開始咱們的改造:
未改造前
父組件:
import React, { useState } from 'react'; import useForm from 'rc-form-hooks'; import { Form, Input, Radio } from 'antd'; import Child from './Child'; const Parent = () => { const form = useForm(); const { getFieldDecorator } = form; const [sourceType, setSourceType] = useState<number>(0); // 切換資源分類 const onChange = e => { const { value } = e.target; setSourceType(value); }; return ( <Form> <Form.Item label="資源名稱"> {getFieldDecorator('title', { rules: [{ required: true, message: '請輸入資源名稱' }], })(<Input placeholder="請輸入資源名稱" maxLength={45} />)} </Form.Item> <Form.Item label="資源分類"> {getFieldDecorator('type', { rules: [{ required: true }], initialValue: 0, })( <Radio.Group onChange={onChange}> <Radio value={0}>A類</Radio> <Radio value={1}>B類</Radio> </Radio.Group>, )} </Form.Item> <Child sourceType={sourceType} /> </Form> ); }; export default Parent; 複製代碼
子組件:
import React from 'react'; interface IProps { sourceType: number; } const Child: React.FC<IProps> = props => { console.log('子組件渲染', props.sourceType); return <></>; }; export default Child; 複製代碼
頁面展現:
初次進入父組件頁面:
當更改了非子組件的props後:
當更改了子組件的props後:
改造後
父組件同上,子組件以下改變:
import React from 'react'; interface IProps { sourceType: number; } const Child: React.FC<IProps> = React.memo(props => { console.log('子組件渲染', props.sourceType); return <></>; }); export default Child; 複製代碼
改造後從新刷新父組件,查看子組件渲染狀況,此時已經沒有其餘多餘的渲染:
props升級到多層嵌套參數
上面咱們試的都是單層的數據,
React.memo
默認作到淺比較,若是咱們的參數是相似對象的多層嵌套,那就須要使用到它的第二個參數了
父組件只改動一下這裏:
<Child sourceType={{ a: { b: sourceType } }} /> 複製代碼
重點在子組件,
React.memo
的第二個參數,我當前只想到了JSON.stringify
,各位掘友有其餘更優雅的方式能夠告訴我哦😄:
import React from 'react'; interface IProps { sourceType: { a: { b: number; }; }; } const Child: React.FC<IProps> = React.memo( props => { console.log('子組件渲染', props.sourceType.a.b); return <></>; }, (precProps, nextProps) => JSON.stringify(precProps) === JSON.stringify(nextProps), ); export default Child; 複製代碼
當切換與子組件無關當數據後,子組件再也不渲染:
只有當子組件依賴的props
參數改變後,纔會執行從新渲染
哈哈,是否是很神奇,我上面的例子是用在函數組件
hooks
中,那class
組件,推薦使用PureComponent
,相同的效果
import { PureComponent, Fragment, } from 'react' @Form.create() class Search extends PureComponent { constructor(props) { super(props) } render() { return ( <Fragment>內容</Fragment> ) } } export default Search 複製代碼
今天在逛掘金時,發現本身居然漏了這個
lazy
的方法,說明本身看的仍是不夠多,還須要須要須要努力啊!!💪
今天就來講說這個懶加載,咱們開發的過程當中,父子組件嵌套的狀況通常比較頻繁,但有時咱們的子組件是須要在必定場景纔會顯示的,那麼咱們就儘可能不讓它渲染,減小父組件頁面的渲染的負載。
React.lazy
必須經過調用動態的import()
加載一個函數,此時會返回一個Promise
, 並解析(resolve)
爲一個帶有包含React
組件的默認導出的模塊。 ----Reactjs
官網
這裏還須要說明兩點:
Suspense
來包裹子組件,以此讓react
得知哪些內容是須要懶加載的;Suspense
的屬性fallback
不能爲空,fallback
屬性存放等待子組件加載時呈現出的元素;我先給你們上一下代碼:
父組件:
import React, { useState, lazy, Suspense } from 'react'; import { Button } from 'antd'; const Test = lazy(() => import('./test')); const App = () => { const [visible, setVisible] = useState<boolean>(false); return ( <> <Button type="primary" onClick={() => { setVisible(true); }} > 切換 </Button> {visible && ( <Suspense fallback={<div>Loading...</div>}> <Test /> </Suspense> )} </> ); }; export default App; 複製代碼
子組件(一個普通的子組件):
import React, { useEffect } from 'react'; const TestView = () => { useEffect(() => { console.log('子組件的渲染'); }, []); return <div>我是測試的頁面</div>; }; export default TestView; 複製代碼
正常的父組件加載完,能夠看到咱們的子組件並無渲染:
當咱們點擊「切換」,子組件異步加載出來了:
參考文章:
寫到此處,並無結束哦!接下來我還會持續追加,看文章的小夥伴們能夠添加一下關注哦!
做者:Christine
出處:https://juejin.cn/post/6844903512493539341
版權全部,歡迎保留原文連接進行轉載:)
複製代碼
若是你對我對文章感興趣或者有些建議想說給我聽👂,也能夠添加一下微信哦!
郵箱:christine_lxq@sina.com
最後:
祝各位工做順利!
-小菜鳥Christine
複製代碼