在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给 UI 组件进行渲染。在我看来,使用 React Hooks 相比于从前的类组件有以下几点好处:
代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
组件树层级变浅,在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现
配置环境 第一步:安装 node.js 官网下载安装https://nodejs.org/en/
第二步:安装 create-react-app 脚手架工具 cnpm install -g create-react-app
第三步:创建 react 项目
在npm start
启动项目时可能会出错
删除 node_modules 文件夹,在项目下的命令行输出入cnpm install
重新安装依赖包
第四步:精简代码 vscode 打开项目,精简代码
demo01\src\Example.js 新建一个文件
Example.js 中原来 react 写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import React, { Component } from 'react' export default class Example extends Component { constructor (props ) { super (props) this .state = { count : 0 } } render ( ) { return ( <div> <p> You clicked {this .state.count} times</p> <button onClick={this .addCount.bind(this )}>Click me</button> </div> ) } addCount ( ) { this .setState({ count : this .state.count + 1 }) } }
并在 inde.js 中调用import Example from './Example'
以及<Example>
hooks 写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import React, { useState } from 'react' function Example ( ) { console .log('函数执行' ) const [count, setCount] = useState(0 ) return ( <div> <p> You clicked {count} times</p> <button onClick={() => { setCount(count + 1 ) }}> Click me </button> </div> ) }export default Example
useState 有两个参数[count,setCount]
, count 就是元素的状态,setCount 是更新元素状态的方法,const [count, setCount] = useState(0)
//是 ES6 解构赋值写法,useState(0)等于{ count: 0 }
Example()函数执行几次? n+1 次 因为 React 底层做了处理,useState 执行时有缓存,将上一次执行的状态保存,不会将 count 重新设置成 0,会继续累加
hooks 是 react 新特性,react16.8 以上才可以用
useState 在 demo01\src\Example2.js 中,先写一个错误例子useState
不能用在条件语句中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import React, { useState } from 'react' let showSex = true function Example2 ( ) { const [age, setAge] = useState(18 ) if (showSex) { const [sex, setSex] = useState('男' ) showSex = false } const [work, setWork] = useState('程序员' ) return ( <div> <p>JS今年:{age}</p> <p>性别:{sex}</p> <p>工作:{work}</p> </div> ) }export default Example2
正确写法就是 React Hooks 不能出现在条件判断语句中,因为它必须有完全一样的渲染顺序 。
import React, { useState } from 'react' function Example2 ( ) { const [age, setAge] = useState(18 ) const [sex, setSex] = useState('男' ) const [work, setWork] = useState('程序员' ) return ( <div> <p>JS今年:{age}</p> <p>性别:{sex}</p> <p>工作:{work}</p> </div> ) }export default Example2
useEffect 代替常用生命周期函数 useEffect 是异步
原始的方式 先用原始的方式把计数器的 Demo 增加两个生命周期函数componentDidMount
和componentDidUpdate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import React, { Component } from 'react' export default class Example extends Component { constructor (props ) { super (props) this .state = { count : 0 } } componentDidMount ( ) { console .log(`componentDidMount=>You clicked ${this .state.count} times` ) } componentDidUpdate ( ) { console .log(`componentDidUpdate=>You clicked ${this .state.count} times` ) } render ( ) { return ( <div> <p> You clicked {this .state.count} times</p> <button onClick={this .addCount.bind(this )}>Click me</button> </div> ) } addCount ( ) { this .setState({ count : this .state.count + 1 }) } }
useEffect 实现 componentDidUpdate 生命周期函数 useEffect
不传参数时,监听所有的状态,类似componentDidUpdate
,只要页面的 state 或者 model 中的 state 中定义的变量值发生改变,这个方法就会执行
useEffect(() => { console .log('componentDidUpdate' ) })
useEffect 实现 componentDidMonut 生命周期函数 useEffect
传第二个参数时[ ],第二个参数是要监听哪些状态的改变,类似componentDidMonut
,组件挂在完成后执行
useEffect(() => { console .log('componentDidMonut' ) }, [])
使用React Hooks
的情况下 我们可以使用下面的代码来完成上边代码的生命周期效果,代码如下(修改了以前的 diamond): 记得要先引入useEffect
后,才可以正常使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import React, { useState, useEffect } from 'react' function Example ( ) { const [count, setCount] = useState(0 ) useEffect(() => { console .log(`useEffect=>You clicked ${count} times` ) }) return ( <div> <p>You clicked {count} times</p> <button onClick={() => { setCount(count + 1 ) }}> click me </button> </div> ) }export default Example
useEffect 两个注意点
React 首次渲染和之后的每次渲染都会调用一遍useEffect
函数,而之前我们要用两个生命周期函数分别表示首次渲染(componentDidMonut)和更新导致的重新渲染(componentDidUpdate)。
useEffect 中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而componentDidMonut
和componentDidUpdate
中的代码都是同步执行的。个人认为这个有好处也有坏处吧,比如我们要根据页面的大小,然后绘制当前弹出窗口的大小,如果时异步的就不好操作了。
useEffect 实现 componentWillUnmount 生命周期函数 在 useEffect 函数里return ( )=>{ }
再 return 一个函数,这个函数会在组件卸载之前执行类似componentWillUnmount
useEffect(() => { return () => { console .log('componentWillUnmount.....' ) } }, [])
在写 React 应用的时候,在组件中经常用到componentWillUnmount
生命周期函数(组件将要被卸载时执行)。比如我们的定时器要清空,避免发生内存泄漏;比如登录状态要取消掉,避免下次进入信息出错。定时器没有关闭等
useEffect 关闭定时器 React.useEffect(() => { let timer = setInterval (() => { setCount(count => count + 1 ) }, 1000 ) return () => { clearInterval (timer) } }, [])
useEffect 解绑副作用 学习React Hooks
时,我们要改掉生命周期函数的概念(人往往有先入为主的毛病,所以很难改掉),因为Hooks
叫它副作用,所以componentWillUnmount
也可以理解成解绑副作用。这里为了演示用useEffect
来实现类似componentWillUnmount
效果,先安装React-Router
路由,进入项目根本录,使用npm
进行安装。
npm install --save react-router-dom
然后打开Example.js
文件,进行改写代码,先引入对应的React-Router
组件。
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
在文件中编写两个新组件,因为这两个组件都非常的简单,所以就不单独建立一个新的文件来写了。
function Index ( ) { return <h2 > JSPang.com</h2 > }function List ( ) { return <h2 > List-Page</h2 > }
有了这两个组件后,接下来可以编写路由配置,在以前的计数器代码中直接增加就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 return ( <div> <p>You clicked {count} times</p> <button onClick={() => { setCount(count + 1 ) }}> click me </button> <Router> <ul> <li> {' ' } <Link to="/" >首页</Link>{' ' } </li> <li> <Link to="/list/" >列表</Link>{' ' } </li> </ul> <Route path="/" exact component={Index} /> <Route path="/list/" component={List} /> </Router> </div> )
然后到浏览器中查看一下,看看组件和路由是否可用。如果可用,我们现在可以调整useEffect
了。在两个新组件中分别加入useEffect()
函数:
function Index ( ) { useEffect(()=> { console .log('useEffect=>老弟,你来了!Index页面' ) ) return <h2 > JSPang.com</h2 > ; }function List ( ) { useEffect(()=> { console .log('useEffect=>老弟,你来了!List页面' ) }) return <h2 > List-Page</h2 > ; }
这时候我们点击Link
进入任何一个组件,在浏览器中都会打印出对应的一段话。这时候可以用返回一个函数的形式进行解绑 ,代码如下:
function Index ( ) { useEffect(() => { console .log('useEffect=>老弟你来了!Index页面' ) return () => { console .log('老弟,你走了!Index页面' ) } }) return <h2 > JSPang.com</h2 > }
这时候你在浏览器中预览,我们仿佛实现了componentWillUnmount
方法。但这只是好像实现了,当点击计数器按钮时,你会发现老弟,你走了!Index页面
,也出现了。这到底是怎么回事那?其实每次状态发生变化,useEffect
都进行了解绑。
useEffect 的第二个参数 那到底要如何实现类似componentWillUnmount
的效果那?这就需要请出useEffect
的第二个参数,它是一个数组,数组中可以写入很多状态对应的变量,意思是当状态值发生变化时,我们才进行解绑。但是当传空数组[]
时,就是当组件将被销毁时才进行解绑,这也就实现了componentWillUnmount
的生命周期函数。
function Index ( ) { useEffect(() => { console .log('useEffect=>老弟你来了!Index页面' ) return () => { console .log('老弟,你走了!Index页面' ) } }, []) return <h2 > JSPang.com</h2 > }
为了更加深入了解第二个参数的作用,把计数器的代码也加上useEffect
和解绑方法,并加入第二个参数为空数组。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function Example ( ) { const [count, setCount] = useState(0 ) useEffect(() => { console .log(`useEffect=>You clicked ${count} times` ) return () => { console .log('====================' ) } }, []) return ( <div> <p>You clicked {count} times</p> <button onClick={() => { setCount(count + 1 ) }}> click me </button> <Router> <ul> <li> {' ' } <Link to="/" >首页</Link>{' ' } </li> <li> <Link to="/list/" >列表</Link>{' ' } </li> </ul> <Route path="/" exact component={Index} /> <Route path="/list/" component={List} /> </Router> </div> ) }
这时候的代码是不能执行解绑副作用函数的。但是如果我们想每次count
发生变化,我们都进行解绑,只需要在第二个参数的数组里加入count
变量就可以了。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function Example ( ) { const [count, setCount] = useState(0 ) useEffect(() => { console .log(`useEffect=>You clicked ${count} times` ) return () => { console .log('====================' ) } }, [count]) return ( <div> <p>You clicked {count} times</p> <button onClick={() => { setCount(count + 1 ) }}> click me </button> <Router> <ul> <li> {' ' } <Link to="/" >首页</Link>{' ' } </li> <li> <Link to="/list/" >列表</Link>{' ' } </li> </ul> <Route path="/" exact component={Index} /> <Route path="/list/" component={List} /> </Router> </div> ) }
这时候只要count
状态发生变化,都会执行解绑副作用函数,浏览器的控制台也就打印出了一串=================
。
useContext 让父子组件传值更简单 有了useState
和useEffect
已经可以实现大部分的业务逻辑了,但是React Hooks
中还是有很多好用的Hooks
函数的,比如useContext
和useReducer
。
在用类声明组件时,父子组件的传值是通过组件属性和props
进行的,那现在使用方法(Function)来声明组件,已经没有了constructor
构造函数也就没有了 props 的接收,那父子组件的传值就成了一个问题。React Hooks
为我们准备了useContext
。这节课就学习一下useContext
,它可以帮助我们跨越组件层级直接传递变量,实现共享。需要注意的是useContext
和redux
的作用是不同的,一个解决的是组件之间值传递的问题,一个是应用中统一管理状态的问题,但通过和useReducer
的配合使用,可以实现类似Redux
的作用。
这就好比玩游戏时有很多英雄,英雄的最总目的都是赢得比赛,但是作用不同,有负责输出的,有负责抗伤害的,有负责治疗的。
Context
的作用就是对它所包含的组件树提供全局共享数据的一种技术。
createContext 函数创建 context 直接在src
目录下新建一个文件Example4.js
,然后拷贝Example.js
里的代码,并进行修改,删除路由部分和副作用的代码,只留计数器的核心代码就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import React, { useState, useEffect } from 'react' function Example4 ( ) { const [count, setCount] = useState(0 ) return ( <div> <p>You clicked {count} times</p> <button onClick={() => { setCount(count + 1 ) }}> click me </button> </div> ) }export default Example4
然后修改一下index.js
让它渲染这个Example4.js
组件,修改的代码如下。
import React from 'react' import ReactDOM from 'react-dom' import Example from './Example4' ReactDOM.render(<Example /> , document .getElementById('root' ))
之后在Example4.js
中引入createContext
函数,并使用得到一个组件,然后在return
方法中进行使用。先看代码,然后我再解释。 创建Context
容器对象要在祖组件和后代组件都能调用的地方const CountContext = createContext()//CountContext首字母大写,因为是组件
在祖组件中使用<CountContext.Provider value={count}> </CountContext.Provider>
,将count
传递给后代组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import React, { useState, createContext } from 'react' const CountContext = createContext() function Example4 ( ) { const [count, setCount] = useState(0 ) return ( <div> <p>You clicked {count} times</p> <button onClick={() => { setCount(count + 1 ) }}> click me </button> {} <CountContext.Provider value={count}></CountContext.Provider> </div> ) }export default Example4
这段代码就相当于把count
变量允许跨层级实现传递和使用了(也就是实现了上下文),当父组件的count
变量发生变化时,子组件也会发生变化。接下来我们就看看一个React Hooks
的组件如何接收到这个变量。
useContext 接收上下文变量 已经有了上下文变量,剩下的就时如何接收了,接收这个直接使用 useContext 就可以,但是在使用前需要新进行引入useContext
(不引入是没办法使用的)。
import React, { useState, createContext, useContext } from 'react'
引入后写一个Counter
组件,只是显示上下文中的count
变量代码如下:
function Counter ( ) { const count = useContext(CountContext) return <h2 > {count}</h2 > }
得到后就可以显示出来了,但是要记得在<CountContext.Provider>
的闭合标签中,代码如下。
<CountContext.Provider value={count}> <Counter /> </CountContext.Provider>
其实useContext
的用法比以前时简单很多,既然简单,就没必要讲解的那么难,希望小伙伴这节课都能 get 到知识点,完善自己的知识体系。
useReducer 介绍和简单使用 上节课学习了useContext
函数,那这节课开始学习一下useReducer
,因为他们两个很像,并且合作可以完成类似的 Redux 库的操作。在开发中使用useReducer
可以让代码具有更好的可读性和可维护性,并且会给测试提供方便。那我们彻底的学习一下useReducer
。这节课我们只是简单的学习一下useReducer
语法和使用方法,尽量避免Redux
的一些操作。这样讲更容易让不了解Redux
的小伙伴接受。
reducer 到底是什么? 为了更好的理解useReducer
,所以先要了解 JavaScript 里的Redcuer
是什么。它的兴起是从Redux
广泛使用开始的,但不仅仅存在Redux
中,可以使用 JavaScript 来完成Reducer
操作。那reducer
其实就是一个函数,这个函数接收两个参数,一个是状态,一个用来控制业务逻辑的判断参数。我们举一个最简单的例子。
function countReducer (state, action ) { switch (action.type) { case 'add' : return state + 1 case 'sub' : return state - 1 default : return state } }
上面的代码就是 Reducer,你主要理解的就是这种形式和两个参数的作用,一个参数是状态,一个参数是如何控制状态。
useReducer 的使用 了解 reducer 的含义后,就可以讲 useReducer 了,它也是 React hooks 提供的函数,可以增强我们的Reducer
,实现类似 Redux 的功能。我们新建一个Example5.js
的文件,然后用 useReducer 实现计数器的加减双向操作。(此部分代码的介绍可以看视频来学习)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import React, { useReducer } from 'react' function ReducerDemo ( ) { const [count, dispatch] = useReducer((state, action ) => { switch (action) { case 'add' : return state + 1 case 'sub' : return state - 1 default : return state } }, 0 ) return ( <div> <h2>现在的分数是{count}</h2> <button onClick={() => dispatch('add' )}>Increment</button> <button onClick={() => dispatch('sub' )}>Decrement</button> </div> ) }export default ReducerDemo
这段代码是 useReducer 的最简单实现了,这时候可以在浏览器中实现了计数器的增加减少。
修改index.js
文件,让ReducerDemo
组件起作用。
import React from 'react' import ReactDOM from 'react-dom' import Example from './Example5' ReactDOM.render(<Example /> , document .getElementById('root' ))
这节课就先到这里,小伙伴们一定对useReducer
有所了解啦,那下节课我们在来用一个具体的例子,实现类似Redux
的用法。
useReducer 代替 Redux 小案例-1 使用useContext
和useReducer
是可以实现类似Redux
的效果,并且一些简单的个人项目,完全可以用下面的方案代替 Redux,这种做法要比 Redux 简单一些。因为useContext
和useReducer
在前两节课已经学习过了,所以我们这节课把精力就放在如何模拟出Redux
的效果。如果你目前还不能掌握基本的语法,可以再复习一下前两节的知识点。
本节课程参考了掘金上缪宇的文章 ,文章地址:https://juejin.im/post/5ceb37c851882520724c7504
理论上的可行性 我们先从理论层面看看替代Redux
的可能性,其实如果你对两个函数有所了解,只要我们巧妙的结合,这种替代方案是完全可行的。
useContext
:可访问全局状态,避免一层层的传递状态。这符合Redux
其中的一项规则,就是状态全局化,并能统一管理。
useReducer
:通过 action 的传递,更新复杂逻辑的状态,主要是可以实现类似Redux
中的Reducer
部分,实现业务逻辑的可行性。
经过我们在理论上的分析是完全可行的,接下来我们就用一个简单实例来看一下具体的实现方法。那这节课先实现useContext
部分(也就是状态共享),下节再继续讲解useReducer
部分(控制业务逻辑)。
编写基本 UI 组件 既然是一个实例,就需要有些界面的东西,小伙伴们不要觉的烦。在/src
目录下新建一个文件夹Example6
,有了文件夹后,在文件夹下面建立一个showArea.js
文件。代码如下:
import React from 'react' function ShowArea ( ) { return <div style ={{ color: 'blue ' }}> 字体颜色为blue</div > }export default ShowArea
显示区域写完后,新建一个Buttons.js
文件,用来编写按钮,这个是两个按钮,一个红色一个黄色。先不写其他任何业务逻辑。
import React from 'react' function Buttons ( ) { return ( <div> <button>红色</button> <button>黄色</button> </div> ) }export default Buttons
然后再编写一个组合他们的Example6.js
组件,引入两个新编写的组件ShowArea
和Buttons
,并用<div>
标签给包裹起来。
import React, { useReducer } from 'react' import ShowArea from './ShowArea' import Buttons from './Buttons' function Example6 ( ) { return ( <div> <ShowArea /> <Buttons /> </div> ) }export default Example6
这步做完,需要到/src
目录下的index.js
中引入一下Example6.js
文件,引入后 React 才能正确渲染出刚写的 UI 组件。
import React from 'react' import ReactDOM from 'react-dom' import Example from './Example6/Example6' ReactDOM.render(<Example /> , document .getElementById('root' ))
做完这步可以简单的预览一下 UI 效果,虽然很丑,但是只要能满足学习需求就可以了。我们虽然都是前端,但是在学习时没必要追求漂亮的页面,关键时把知识点弄明白。我们写这么多文件,也就是要为接下来的知识点服务,其实这些组件都是陪衬罢了。
编写颜色共享组件color.js
有了 UI 组件后,就可以写一些业务逻辑了,这节课我们先实现状态共享,这个就是利用useContext
。建立一个color.js
文件,然后写入下面的代码。
import React, { createContext } from 'react' export const ColorContext = createContext({})export const Color = props => { return <ColorContext.Provider value ={{ color: 'blue ' }}> {props.children}</ColorContext.Provider > }
代码中引入了createContext
用来创建共享上下文ColorContext
组件,然后我们要用{props.children}
来显示对应的子组件。详细解释我在视频中讲解吧。
有了这个组件后,我们就可以把Example6.js
进行改写,让她可以共享状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import React, { useReducer } from 'react' import ShowArea from './ShowArea' import Buttons from './Buttons' import { Color } from './color' function Example6 ( ) { return ( <div> <Color> <ShowArea /> <Buttons /> </Color> </div> ) }export default Example6
然后再改写showArea.js
文件,我们会引入useContext
和在color.js
中声明的ColorContext
,让组件可以接收全局变量。
import React, { useContext } from 'react' import { ColorContext } from './color' function ShowArea ( ) { const { color } = useContext(ColorContext) return <div style ={{ color: color }}> 字体颜色为{color}</div > }export default ShowArea
这时候就通过useContext
实现了状态的共享,可以到浏览器中看一下效果。然后我们下节课再实现复杂逻辑状态的变化。
useReducer 代替 Redux 小案例-2 在 color.js 中添加 Reducer 颜色(state)管理的代码我们都放在了color.js
中,所以在文件里添加一个 reducer,用于处理颜色更新的逻辑。先声明一个 reducer 的函数,它就是 JavaScript 中的普通函数,在讲useReducer
的时候已经详细讲过了。有了 reducer 后,在 Color 组件里使用useReducer
,这样 Color 组件就有了那个共享状态和处理业务逻辑的能力,跟以前使用的Redux
几乎一样了。之后修改一下共享状态。我们来看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import React, { createContext, useReducer } from 'react' export const ColorContext = createContext({})export const UPDATE_COLOR = 'UPDATE_COLOR' const reducer = (state, action ) => { switch (action.type) { case UPDATE_COLOR: return action.color default : return state } }export const Color = props => { const [color, dispatch] = useReducer(reducer, 'blue' ) return <ColorContext.Provider value ={{ color , dispatch }}> {props.children}</ColorContext.Provider > }
注意,这时候我们共享出去的状态变成了 color 和 dispatch,如果不共享出去 dispatch,你是没办法完成按钮的相应事件的。
通过 dispatch 修改状态 目前程序已经有了处理共享状态的业务逻辑能力,接下来就可以在buttons.js
使用dispatch
来完成按钮的相应操作了。先引入useContext
、ColorContext
和UPDATE_COLOR
,然后写onClick
事件就可以了。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import React, { useContext } from 'react' import { ColorContext, UPDATE_COLOR } from './color' function Buttons ( ) { const { dispatch } = useContext(ColorContext) return ( <div> <button onClick={() => { dispatch({ type : UPDATE_COLOR, color : 'red' }) }}> 红色 </button> <button onClick={() => { dispatch({ type : UPDATE_COLOR, color : 'yellow' }) }}> 黄色 </button> </div> ) }export default Buttons
这样代码就编写完成了,用useContext
和useReducer
实现了 Redux 的效果,这个代码编写过程比 Redux 要简单,但是也是有一定难度的。希望第一次接触的小伙伴能自己动手写 5 遍以上,把这种模式掌握好。
useMemo 优化 React Hooks 程序性能 useMemo
主要用来解决使用 React hooks 产生的无用渲染的性能问题。使用 function 的形式来声明组件,失去了shouldCompnentUpdate
(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分mount
和update
两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。useMemo
和useCallback
都是解决上述性能问题的,这节课先学习useMemo
.
性能问题展示案例 先编写一下刚才所说的性能问题,建立两个组件,一个父组件一个子组件,组件上由两个按钮,一个是小红,一个是志玲,点击哪个,那个就像我们走来了。在/src
文件夹下,新建立一个Example7
的文件夹,在文件夹下建立一个Example7.js
文件.然后先写第一个父组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import React, { useState, useMemo } from 'react' function Example7 ( ) { const [xiaohong, setXiaohong] = useState('小红待客状态' ) const [zhiling, setZhiling] = useState('志玲待客状态' ) return ( <> <button onClick={() => { setXiaohong(new Date ().getTime()) }}> 小红 </button> <button onClick={() => { setZhiling(new Date ().getTime() + ',志玲向我们走来了' ) }}> 志玲 </button> <ChildComponent name={xiaohong}>{zhiling}</ChildComponent> </> ) }
父组件调用了子组件,子组件我们输出两个姑娘的状态,显示在界面上。代码如下:
function ChildComponent ({ name, children } ) { function changeXiaohong (name ) { console .log('她来了,她来了。小红向我们走来了' ) return name + ',小红向我们走来了' } const actionXiaohong = changeXiaohong(name) return ( <> <div>{actionXiaohong}</div> <div>{children}</div> </> ) }
然后再导出父组件,让index.js
可以渲染。
这时候你会发现在浏览器中点击志玲
按钮,小红对应的方法都会执行,结果虽然没变,但是每次都执行,这就是性能的损耗。目前只有子组件,业务逻辑也非常简单,如果是一个后台查询,这将产生严重的后果。所以这个问题必须解决。当我们点击志玲
按钮时,小红对应的changeXiaohong
方法不能执行,只有在点击小红
按钮时才能执行。
useMemo 优化性能 其实只要使用useMemo
,然后给她传递第二个参数,参数匹配成功,才会执行。代码如下:
function ChildComponent ({ name, children } ) { function changeXiaohong (name ) { console .log('她来了,她来了。小红向我们走来了' ) return name + ',小红向我们走来了' } const actionXiaohong = useMemo(() => changeXiaohong(name), [name]) return ( <> <div>{actionXiaohong}</div> <div>{children}</div> </> ) }
这时在浏览器中点击一下志玲
按钮,changeXiaohong
就不再执行了。也节省了性能的消耗。案例只是让你更好理解,你还要从程序本身看到优化的作用。好的程序员对自己写的程序都是会进行不断优化的,这种没必要的性能浪费也是绝对不允许的,所以useMemo
的使用在工作中还是比较多的。希望小伙伴们可以掌握。
useRef 获取 DOM 元素和保存变量
useRef
在工作中虽然用的不多,但是也不能缺少。它有两个主要的作用:
用useRef
获取 React JSX 中的 DOM 元素,获取后你就可以控制 DOM 的任何东西了。但是一般不建议这样来作,React 界面的变化可以通过状态来控制。
用useRef
来保存变量,这个在工作中也很少能用到,我们有了useContext
这样的保存其实意义不大,但是这是学习,也要把这个特性讲一下。
界面上有一个文本框,在文本框的旁边有一个按钮,当我们点击按钮时,在控制台打印出input
的 DOM 元素,并进行复制到 DOM 中的 value 上。这一切都是通过useRef
来实现。
在/src
文件夹下新建一个Example8.js
文件,然后先引入 useRef,编写业务逻辑代码如下:
当点击按钮时,你可以看到在浏览器中的控制台完整的打印出了 DOM 的所有东西,并且界面上的<input/>
框的 value 值也输出了我们写好的Hello ,JSPang
。这一切说明我们可以使用 useRef 获取 DOM 元素,并且可以通过 useRef 控制 DOM 的属性和值。
这个操作在实际开发中用的并不多,但我们还是要讲解一下。就是useRef
可以保存 React 中的变量。我们这里就写一个文本框,文本框用来改变text
状态。又用useRef
把text
状态进行保存,最后打印在控制台上。写这段代码你会觉的很绕,其实显示开发中没必要这样写,用一个 state 状态就可以搞定,这里只是为了展示知识点。
接着上面的代码来写,就没必要重新写一个文件了。先用useState
声明了一个text
状态和setText
函数。然后编写界面,界面就是一个文本框。然后输入的时候不断变化。
这时想每次text
发生状态改变,保存到一个变量中或者说是useRef
中,这时候就可以使用useRef
了。先声明一个textRef
变量,他其实就是useRef
函数。然后使用useEffect
函数实现每次状态变化都进行变量修改,并打印。最后的全部代码如下。
这时候就可以实现每次状态修改,同时保存到useRef
中了。也就是我们说的保存变量的功能。那useRef
的主要功能就是获得 DOM 和变量保存,我们都已经讲过了。你的编码能力有增加了一些,让我们一起加油。
useRef 获取 DOM 元素 import React, { useRef } from 'react' function Example8 ( ) { const inputEl = useRef(null ) const onButtonClick = () => { inputEl.current.value = 'Hello ,JSPang' console .log(inputEl) } return ( <> {} <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>在input上展示文字</button> </> ) }export default Example8
useRef 保存普通变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import React, { useRef, useState, useEffect } from 'react' function Example8 ( ) { const inputEl = useRef(null ) const onButtonClick = () => { inputEl.current.value = 'Hello ,useRef' console .log(inputEl) } const [text, setText] = useState('jspang' ) return ( <> {} <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>在input上展示文字</button> <br /> <br /> <input value={text} onChange={e => { setText(e.target.value) }} /> </> ) }export default Example8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import React, { useRef, useState, useEffect } from 'react' function Example8 ( ) { const inputEl = useRef(null ) const onButtonClick = () => { inputEl.current.value = 'Hello ,useRef' console .log(inputEl) } const [text, setText] = useState('jspang' ) const textRef = useRef() useEffect(() => { textRef.current = text console .log('textRef.current:' , textRef.current) }) return ( <> {} <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>在input上展示文字</button> <br /> <br /> <input value={text} onChange={e => { setText(e.target.value) }} /> </> ) }export default Example8
自定义 Hooks 函数获取窗口大小 其实自定义 Hooks 函数和用 Hooks 创建组件很相似,跟我们平时用 JavaScript 写函数几乎一模一样,可能就是多了些React Hooks
的特性,自定义 Hooks 函数偏向于功能,而组件偏向于界面和业务逻辑。由于差别不大,所以使用起来也是很随意的。如果是小型项目是可以的,但是如果项目足够复杂,这会让项目结构不够清晰。所以学习自定义 Hooks 函数还是很有必要的。
编写自定义函数 在实际开发中,为了界面更加美观。获取浏览器窗口的尺寸是一个经常使用的功能,这样经常使用的功能,就可以封装成一个自定义Hooks
函数,记住一定要用 use 开头,这样才能区分出什么是组件,什么是自定义函数。
新建一个文件Example9.js
,然后编写一个 useWinSize,编写时我们会用到useState
、useEffect
和useCallback
所以先用import
进行引入。
import React, { useState, useEffect, useCallback } from 'react'
然后编写函数,函数中先用 useState 设置size
状态,然后编写一个每次修改状态的方法onResize
,这个方法使用useCallback
,目的是为了缓存方法(useMemo 是为了缓存变量)。 然后在第一次进入方法时用useEffect
来注册resize
监听时间。为了防止一直监听所以在方法移除时,使用 return 的方式移除监听。最后返回 size 变量就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function useWinSize ( ) { const [size, setSize] = useState({ width: document .documentElement.clientWidth, height: document .documentElement.clientHeight, }) const onResize = useCallback(() => { setSize({ width: document .documentElement.clientWidth, height: document .documentElement.clientHeight, }) }, []) useEffect(() => { window .addEventListener('resize' , onResize) return () => { window .removeEventListener('resize' , onResize) } }, []) return size }
这就是一个自定义函数,其实和我们以前写的 JS 函数没什么区别,所以这里也不做太多的介绍。
编写组件并使用自定义函数 自定义Hooks
函数已经写好了,可以直接进行使用,用法和JavaScript
的普通函数用起来是一样的。直接在Example9
组件使用useWinSize
并把结果实时展示在页面上。
function Example9 ( ) { const size = useWinSize() return ( <div> 页面Size:{size.width}x{size.height} </div> ) }export default Example9
之后就可以在浏览器中预览一下结果,可以看到当我们放大缩小浏览器窗口时,页面上的结果都会跟着进行变化。说明自定义的函数起到了作用。