React之Hooks---(参考jsPang)
前言:
React Hooks是React生态圈近期里边最火的新特性之一,FaceBook于2018年底推出。
它改变了原始的React类的开发方式,改用了函数形式;
它改变了复杂的状态操作形式,让程序员用起来更轻松;
它改变了一个状态组件的复用性,让组件的复用性大大增加
大纲:
01_ReactHooks开发环境搭建和HelloWorld编写 02_useState的介绍和多状态声明 03_useEffect代替生命周期函数 04_useEffect实现ComponentWillUnmont生命周期函数 05_useContext父子组件传值React Hook 中 createContext & useContext 跨组件透传上下文与性能优化 06_useReducer介绍和简单使用 07_useReducer代替Redux小案例-1 08_useReducer代替Redux小案例-2 09_useMemo解决子组件重复执行问题 10_useRef获取DOM和保存变量 11_自定义Hooks函数
(1)01_ReactHooks开发环境搭建和HelloWorld编写
首先用脚手架创建项目
>create-react-app hooks-test
然后精简化代码,将其他没用的文件删除,只留下index.js文件
接下来做个案例,如下所示,每点击一次按钮,次数+1,分别用之前的类继承开发方式和Hooks开发方式做下该案例,以此做下对比
首先使用之前的Class继承方式编写,如下所示
此时便可以实现点击按钮类增1
接下来使用Hooks写法编写该案例,注意版本
注意:Hooks是新版react内置特性(V16.8以上才可以使用),所以无需下载其他依赖.
此时便用Hooks形式实现了该案例
(2)02_useState的介绍和多状态声明
以之前案例为依据,如下所示
本节将从3个方面介绍useState:声明、读取、修改(使用进行编辑)。
1、声明
const [count,addCountFn] = useState(0)
useState为一个方法,方法里接受一个初始值0
左侧写法为Es6的数组解构语法,如果不用解构赋值,则ES5语法如下
数组[]里一共有两个值,第一个为初始值,第二个为方法,用来改变初始值。
2、读取
初始值解构出来后可以看作一个变量,可以直接在后面使用,如下所示
3、修改
SetCount方法用于修改初始值,使用时括号里直接写修改后的值即可。
思考:至此可以实现count数量的修改记录,但是代码里看不出来在哪里做了记录?
答案:通过useState的顺序记住的
接下来通过多状态声明做下介绍
此时便声明了多个状态并进行了对应展示,之前也介绍过,useState是通过顺序来记录的,接下来我们做下改动,如下所示
由此可以判断出useState里必须以顺序进行调用,且不支持条件判断,因为useState是按照顺序进行多状态的记录。此外,还可以添加事件处理,如下所示
(3)03_useEffect代替生命周期函数
首先使用之前的Class方式做下生命周期钩子的验证,如下所示
接下来使用useEffect替代生命周期钩子
测试如下
我们会发现初始化时便触发了useEffect,输出点击次数为0。接着进行点击,会依次打印,如下所示
注意: 1、eact-hooks的useEffect一个方法代替了之前Class写法的两个钩子函数,即componentDidMount和componentDidUpdate
两个生命周期函数钩子
2、useEffect方法是异步的,会进行延时处理。但也有坏处,无法实时处理某些场景,如浏览器窗口缩放时,实时计算窗口大小,进行重新布局。
(4)04_useEffect实现ComponentWillUnmount生命周期函数
场景:某个组件里绑定了定时器,当组件销毁时需要对定时器进行解绑,否则影响性能,容易造成内存溢出。
所以本节主要使用useEffect实现componentWillUnmount进行组件销毁前的相关监听操作。接下来首先引入路由,进行路由相关操作,才可以实现组件的销毁过程.
1、下载引入路由react-router-dom
>npm i react-router-dom --save
2、编写两个简单的无状态组件用于换页
因为hooks下组件可以用方法代替
接下来进行路由配置
此时便可以进行切换,如下所示
接着给两个页面方法添加useEffect进行监听
测试如下
此时来回切换页面时便会触发对应的useEffect方法
在组件解绑时,react-hooks设计了对应的方法用于监听组件解绑。在useEffect方法里添加return返回匿名函数,从而监听组件解绑,如下所示
测试如下
我们发现,此时在离开组件时(组件状态更改)便会进行解绑监听。但此时还存在问题,即只要组件状态更改,就会触发该方法的解绑监听,如下所示,我们点击按钮,实现累加效果,会发现每次点击按钮进行累加,状态更改时,都会触发解绑监听.
此时便需要用到useEffect的第二个参数(数组格式),如下所述
useEffect语法:
useEffect(()=>{ ... ... },[[参数1],[参数2]...[参数n]])
分析:
参数1为箭头函数 参数2为数组,里面可以传入参数,也可以不传。如果不传参,则代表整个组件发生改变时才触发该return方法;如果传入参数,则代表除了组件销毁外,
当传入的参数发生改动时也可以触发该return方法。
所以,如果我们想监听组件的销毁前的钩子监听,则使用空数组[],不对其进行传参
此时便可以实现组件的销毁前钩子监听,而不受组件内部状态更改的影响,测试如下
接着我们对数组进行传参,然后绑定事件进行更改,看看是否如我们所想进行触发
此时我们发现,因为传入了content参数,所以当content改动和组件卸载时都会触发return方法。
(5)05_useContext父子组件传值(React Hook 中 createContext & useContext 跨组件透传上下文与性能优化)
有了useState和useEffect已经可以实现大部分的业务逻辑了,但是React Hooks中还是有很多好用的Hooks函数的,比如useContext和useReducer。
在用类声明组件时,父子组件的传值是通过组件属性和props进行的,那现在使用方法(Function)来声明组件,已经没有了constructor构造函数也就没有了props的接收,
那父子组件的传值就成了一个问题。React Hooks 为我们准备了useContext。这节课就学习一下useContext,它可以帮助我们跨越组件层级直接传递变量,实现共享。
需要注意的是useContext和redux的作用是不同的,一个解决的是组件之间值传递的问题,一个是应用中统一管理状态的问题,但通过和useReducer的配合使用,可以实现类似Redux的作用。
之前的Class形式创建组件里,组件间父子组件传值时使用this.props,无状态组件主要使用props进行传值。
在Hooks形式,即function创建的组件里,父子组件传值主要通过useContext.
一般useContext会和useReducer配合使用,useContext主要解决父子组件传值,useReducer主要解决状态共享,实现redux的功能。接下来看下使用步骤
1、先将需要传递的值暴露到上下文context,即父组件通过createContext 函数创建context上下文
引入创建上下文方法createContext,然后创建传递值上下文.此时就可进行共享了,CountContext相当于一个组件
2、父组件里,共享传值上下文
这段代码就相当于把count变量允许跨层级实现传递和使用了(也就是实现了上下文),当父组件的count变量发生变化时,子组件也会发生变化。
接下来我们就看看一个React Hooks的组件如何接收到这个变量。
3、子组件使用useContext接收上下文变量
因为这里要使用createContext创建的上下文,所以需要在父组件所在组件里导出
接下来在子组件导入即可
小结:
以上为讲解时的案例。下面介绍下开发时的使用方法,详见createContext 和 useContext 结合使用实现方法共享.
这里对上述案例做下修改
1、App.js
import React,{useState,createContext} from 'react' /** 1. 引入 createContext 创建的上下文 */ import {CountContext} from './context-manager' import Child from './Child.jsx' /** 2. 创建 Provider */ function App(){ const [count,setCount] = useState(0) return ( <div> <p>You Click {count} times.</p> <button onClick={()=>{setCount(count+1)}}>Click me(父)</button> <CountContext.Provider value={{setCount}}> <Child count={count}></Child> </CountContext.Provider> </div> ) } export default App
2、Child.jsx
import React, { useContext ,Fragment} from 'react'; import {CountContext} from './context-manager' function Child(props){ let {setCount} = useContext(CountContext) return ( <Fragment> <h3>我是子组件,接收父组件的值为:{props.count}</h3> <button onClick={() => { setCount(props.count+ 1) }}>Clike me(子)</button> </Fragment> ) } export default Child
3、context-manager.js
import React from 'react'; /* 使用 createContext 创建上下文 */ export const CountContext = React.createContext(null); /** 或者 export const CountContext = new createContext() */
效果如下
此时子组件可以直接使用上下文中的方法,修改父组件的state.
可以发现,在子组件中点击按钮,直接调用 Context 透传过来的方法,可以修改父组件的 state,子组件则会重新渲染。
这种方式显式的避免了多级 props 的层层透传问题,虽然 Demo 只有一级 子组件,即使存在多级子组件也可以直接修改
(6)06_useReducer介绍和简单使用
这里注意:switch...case里return后不需要break,因为return便会结束函数体,不会造成函数穿透。
useReducer为Hooks新增的方法,增强完善了reducer操作
使用步骤如下
1、引入useReducer方法
2、编写组件---定义reducer
useReducer方法接收两个参数,第一个为箭头函数(内部处理action),第二个为初始值。前面的数组[]里传入两个参数,第一个为箭头函数内部action的返回结果,第二个为派发器dispatch.接下来调用
此时测试发现按钮无效,原因在于switch判断时为对象格式action.type,而我们派发时是字符串
完整代码如下
import React, { useReducer } from 'react' function ReducerDemo(){ const [count,dispatch] = useReducer((state,action)=>{ switch(action){ case 'add': return state+1 case 'cut': return state-1 default: return state } },0) return ( <div> <h2>现在的分数为{count}</h2> <button onClick={()=>dispatch('add')}>add</button> <button onClick={()=>dispatch('cut')}>cut</button> </div> ) } export default ReducerDemo
(7)07_useReducer代替Redux小案例-1
本节利用useReducer和useContext实现一个redux效果(状态管理useReducer和状态共享useContext)
useContext:可访问全局状态,避免一层层的传递状态。这符合Redux其中的一项规则,就是状态全局化,并能统一管理。
useReducer:通过action的传递,更新复杂逻辑的状态,主要是可以实现类似Redux中的Reducer部分,实现业务逻辑的可行性。
由上可知useContext共享状态+useReducer管理状态即可实现redux效果。
1、展示区域
2、按钮组件
3、将之前组件整合
4、引入整合文件并展示
这里已经展示出基本结构,接下来首先做下状态共享useContext.
由此便将color共享了出去,变成了上下文。接下来进行使用
最后动态接受上下文,并进行显示
此时便可以实现动态接收
也可以在color.js进行修改
由此判断,此时状态已经共享出去,接下来使用useReducer进行业务逻辑编写,两者结合便是redux。
其他参考文章:
编写基本UI组件
既然是一个实例,就需要有些界面的东西,小伙伴们不要觉的烦。在/src
目录下新建一个文件夹Example6
,有了文件夹后,在文件夹下面建立一个showArea.js
文件。代码如下:
1
2
3
4
5
6
7
|
import React from 'react' ; function ShowArea(){ return (<div style={{color: 'blue' }}>字体颜色为blue</div>) } export default ShowArea |
显示区域写完后,新建一个Buttons.js
文件,用来编写按钮,这个是两个按钮,一个红色一个黄色。先不写其他任何业务逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
|
import React from 'react' ; function Buttons(){ return ( <div> <button>红色</button> <button>黄色</button> </div> ) } export default Buttons |
然后再编写一个组合他们的Example6.js
组件,引入两个新编写的组件ShowArea
和Buttons
,并用<div>
标签给包裹起来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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组件。
1
2
3
4
5
|
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
文件,然后写入下面的代码。
1
2
3
4
5
6
7
8
9
10
11
|
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
18
|
import React, { useReducer } from 'react' ; import ShowArea from './ShowArea' ; import Buttons from './Buttons' ; import { Color } from './color' ; //引入Color组件 function Example6(){ return ( <div> <Color> <ShowArea /> <Buttons /> </Color> </div> ) } export default Example6 |
然后再改写showArea.js
文件,我们会引入useContext
和在color.js
中声明的ColorContext
,让组件可以接收全局变量。
1
2
3
4
5
6
7
8
9
10
|
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
实现了状态的共享,可以到浏览器中看一下效果。然后我们下节课再实现复杂逻辑状态的变化。
(8)08_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
20
21
22
23
24
|
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
|
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遍以上,把这种模式掌握好。
(9)09_useMemo解决子组件重复执行问题
useMemo优化React Hooks程序性能,解决子组件重复执行问题,详见useMemo优化React Hooks程序性能,解决子组件重复执行问题.
useCallback优化React Hooks程序性能,详见useCallback优化React Hooks程序性能 .
(10)10_useRef获取DOM和保存变量
useRef
在工作中虽然用的不多,但是也不能缺少。它有两个主要的作用:
-
用
useRef
获取React JSX中的DOM元素,获取后你就可以控制DOM的任何东西了。但是一般不建议这样来作,React界面的变化可以通过状态来控制。 -
用
useRef
来保存变量,这个在工作中也很少能用到,我们有了useContext
这样的保存其实意义不大,但是这是学习,也要把这个特性讲一下。
1、useRef获取DOM元素
此时点击按钮时,输入框的value值便发生改动。
界面上有一个文本框,在文本框的旁边有一个按钮,当我们点击按钮时,在控制台打印出input的DOM元素,并进行复制到DOM中的value上。这一切都是通过useRef来实现。
完整代码如下
import React, { useRef} from 'react'; function Example8(){ const inputEl = useRef(null) const onButtonClick=()=>{ inputEl.current.value="Hello ,JSPang" console.log(inputEl) //输出获取到的DOM节点 } return ( <> {/*保存input的ref到inputEl */} <input ref={inputEl} type="text"/> <button onClick = {onButtonClick}>在input上展示文字</button> </> ) } export default Example8
当点击按钮时,你可以看到在浏览器中的控制台完整的打印出了DOM的所有东西,并且界面上的<input/>
框的value值也输出了我们写好的Hello ,JSPang
。这一切说明我们可以使用useRef获取DOM元素,并且可以通过useRefu控制DOM的属性和值。
2、useRef保存普通变量
这个操作在实际开发中用的并不多,但我们还是要讲解一下。就是useRef
可以保存React中的变量。我们这里就写一个文本框,文本框用来改变text
状态。又用useRef
把text
状态进行保存,最后打印在控制台上。写这段代码你会觉的很绕,其实显示开发中没必要这样写,用一个state状态就可以搞定,这里只是为了展示知识点。
接着上面的代码来写,就没必要重新写一个文件了。先用useState
声明了一个text
状态和setText
函数。然后编写界面,界面就是一个文本框。然后输入的时候不断变化。
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 */} <input ref={inputEl} type="text"/> <button onClick = {onButtonClick}>在input上展示文字</button> <br/> <br/> <input value={text} onChange={(e)=>{setText(e.target.value)}} /> </> ) } export default Example8
这时想每次text
发生状态改变,保存到一个变量中或者说是useRef
中,这时候就可以使用useRef
了。先声明一个textRef
变量,他其实就是useRef
函数。然后使用useEffect
函数实现每次状态变化都进行变量修改,并打印。最后的全部代码如下。
import React, { useRef ,useState,useEffect } from 'react'; function Example8(){ const inputEl = useRef(null) const onButtonClick=()=>{ inputEl.current.value="Hello ,useRef" console.log(inputEl) } //-----------关键代码--------start const [text, setText] = useState('jspang') const textRef = useRef() useEffect(()=>{ textRef.current = text; console.log('textRef.current:', textRef.current) }) //----------关键代码--------------end return ( <> {/*保存input的ref到inputEl */} <input ref={inputEl} type="text"/> <button onClick = {onButtonClick}>在input上展示文字</button> <br/> <br/> <input value={text} onChange={(e)=>{setText(e.target.value)}} /> </> ) } export default Example8
这时候就可以实现每次状态修改,同时保存到useRef
中了。也就是我们说的保存变量的功能,这边好似useRef
的主要功能即获得DOM和变量保存。
(11)11_自定义Hooks函数
.