写这篇文章初衷是整理一下本身这近几个月的心路历程,从刚开始(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
复制代码