先来看看类组件的生命周期Class 组件的生命周期
相对以前较老的生命周期,新增了 getDerivedStateFromProps
和 getSnapshotBeforeUpdate
两个生命周期,移除了(目前还未完全移除)三个生命周期:componentWillMount
、componentWillReceiveProps
和 componentWillUpdate
。除此之外,还新增一个 this.forceUpdate(callback)
跳过该组件的 shouldComponentUpdate()
强制执行 render
。
一、为什么改生命周期?
- 移除
componentWillMount
的原因是,初始化 state 可以使用 constructor 代替,发送 Ajax 可以使用componentDidMount
。 - 移除
componentWillUpdate
的原因是:新增的getSnapshotBeforeUpdate
如果有返回值,componentDidUpdate的第三个参数(snapshot)能接受到
,所以getSnapshotBeforeUpdate
就是为了能和conponentDidUpdate
产生关系。 - 移除
componentWillReceiveProps
的原因是:componentWillReceiveProps
使用机会很少,基本只会出现在state
的值在任何时候都取决于props
的情况下。新增的getDerivedStateFromProps
明显是扩充了componentWillReceiveProps
的功能。
更详细的原因,参考:
二、无状态组件(函数式组件)
functional component
一个函数就是一个组件,但是这个函数必须:
- 大写字母开头、必须
Return jsx
。 - 这个函数式组件:没有 state、没有生命周期、有 props。
增加了一种函数的调用方式:
import React,{Component,useState} from "react";
function Fun(){
return <h1>两次调用</h1>
}
export default class App extends Component{
constructor(){
super();
}
render(){
return (
<div>
{Fun()}
<Fun />
</div>
);
}
}
hooks 就是添加了状态和生命周期的函数组件。
三、生命周期方法要如何对应到 Hook?
-
constructor
:初始化 state。hooks 中可使用useState 和 useReducer
代替。 -
render
:就是函数组件本身。 -
getDerivedStateFromProps
:为 在渲染时 安排一次更新,即在render
之前执行一次,对于 hooks 来讲就是在 render 语句上面加个条件判断语句。 -
shouldComponentUpdate
:React.memo 只能浅比较(Object.is
) props,不能对比state, React.memo。 -
componentDidMount
,componentDidUpdate
,componentWillUnmount
:useEffect
Hook 的三种用法。 -
getSnapshotBeforeUpdate
,componentDidCatch
以及getDerivedStateFromError
:目前还没有这些方法的 Hook 等价写法,官网说会很快添加。
四、useState 和 setState 的区别
-
useState
会替换采用的是直接替换模式,setState
采用的是合并
模式。所以 useState 要使用扩展运算符,而且useState
得内容不能过多,多的话分多个useState
。
使用 useState 制作计数器
import React, { Component, useState } from "react";
function Fun() {
const [state, setState] = useState(10);
return <div>
<h1>{state}</h1>
<button onClick = {()=>{setState(state + 1)}}>按我加一</button>
</div>
}
export default class App extends Component{
constructor() {
super();
}
render() {
return (
<div>
<Fun />
</div>
);
}
}
useState 是 react 自带的一个 hook 函数,它的作用就是用来声明状态变量。useState 这个函数接收的参数是我们的状态初始值,它返回了一个数组,这个数组的第 [0] 项是当前当前的状态值,第 [1] 项是可以改变状态值的方法函数。
- 另一个特别重要的就是闭包对 useState 的影响。
源代码:
import React, { useState } from "react";
import ReactDOM from "react-dom";
function A () {
const [count, setCount] = useState(0);
function handleAdd(){
setTimeout(function delay () {
setCount(count + 1);
}, 3000);
};
return (
<>
<h1>{count}</h1>
<button onClick={handleAdd}>加一</button>
</>
)
}
ReactDOM.render(<A />, document.getElementById("container"));
代码演示结果,连续点击三下,最终只变了一次,count 变量不能正确记录实际点击次数,有些点击被吃掉,就是闭包的影响,handleAdd 函数是 A 函数里面的一个函数,典型的闭包函数, handleAdd 函数内部记住了 A 函数的变量。为了解决这个问题,使用函数方法来更新 count 状态。
改写代码:
import React, { useState } from "react";
import ReactDOM from "react-dom";
function A () {
const [count, setCount] = useState(0);
function handleAdd(){
setTimeout(function delay () {
setCount(value => value + 1);
}, 3000);
};
return (
<>
<h1>{count}</h1>
<button onClick={handleAdd}>加一</button>
</>
)
}
ReactDOM.render(<A />, document.getElementById("container"));
此时快速单击按钮。延迟过去后,count 能正确表示点击次数。
三、useEffect 的作用
useEffect 相当于 react 的这三个生命周期( componentDidMount,componentDidUpdate和componentWillUnmount)函数的结合:
- componentDidMount
useEffect(() => {
console.log("componentDidMount");
}, []);
- componentDidMount + componentWillUnmount
useEffect(() => {
console.log("componentDidMount");
return () => {
console.log("componentWillUnmount");
};
}, []);
- componentDidMount 和 componentDidUpdate
useEffect(() => {
console.log("componentDidMount 和 componentDidUpdate");
}, [state]);
一个失败的例子🌰:
import React,{Component,useState,useEffect} from "react";
function Fun(){
const [state, setState] = useState(10);
useEffect(() => {
console.log("观察我的生命周期!");
console.log("componentDidMount,componentDidUpdate和componentWillUnmount");
})
return <div>
<h1>{state}</h1>
<button onClick = {()=>{setState(state + 1)}}>按我加一</button>
</div>
}
export default class App extends Component{
constructor(){
super();
}
render(){
return (
<div>
<Fun />
</div>
);
}
}
四、hook API
不一个一个介绍了,反正自己看直接看写法就行了。
useMemo
和useEffect
的执行时机
useEffect 执行的是副作用,所以一定是在渲染之后执行的,useMemo 是需要有返回值的,而返回值可以直接参与渲染的,所以 useMemo 是在渲染期间完成的,useMemo 先于 useEffect 执行。userMemo
和useCallback
的区别?
共同点:
- 只有在依赖数据发生变化后,才会重新计算结果,起到缓存的作用
- 接受的参数都是一样的,第一个参数为回调函数,第二个参数是要依赖的数据;
区别:
- useMemo 返回的是计算的结果值,用于缓存计算后的状态,一般用于缓存 state。
- useCallback 返回的是函数,主要用来缓存函数。
仔细品,是不是有 Vue computed API 的赶脚,这有一篇介绍 React 中使用 computed 的文章:乾坤大挪移!React 也能 “用上” computed 属性 —— memoize-one 。
- 获取 DOM 的三个 API
-
useRef
函数内容获取 DOM -
useImperativeHandle(ref, createHandle, [deps])
结合React.forwardRef
可以转发经过改写的 ref。React.forwardRef
解决函数组件的实例没有 ref 的问题。
-
useLayoutEffect
与 useEffect 用法相同,但它是在浏览器执行绘制之前,同步触发重渲染,有可能阻塞代码,禁用。 -
useContext
和useReducer
- useContext 多组件共享状态,结合
React.createContext
使用。 - useReducer 就是 redux ,但是相对于 redux 没有中间件功能。
五、调色板
import React,{Component,useState,useEffect} from "react";
function Fun(){
const [r, setR] = useState(10);
const [g, setG] = useState(10);
const [b, setB] = useState(10);
return <div>
<div style = {{
"width":"200px",
"height":"200px",
"backgroundColor":`rgb(${r},${g},${b})`
}}></div>
<p>
R:{r}<input type = "range" min = {"0"} max = {"255"} value={r} onChange = {(e)=>{
setR(e.target.value);
}}/>
</p>
<p>
G:{g}<input type = "range" min = {"0"} max = {"255"} value={g} onChange = {(e)=>{
setG(e.target.value);
}}/>
</p>
<p>
B:{b}<input type = "range" min = {"0"} max = {"255"} value={b} onChange = {(e)=>{
setB(e.target.value);
}}/>
</p>
</div>
}
export default class App extends Component{
constructor(){
super();
this.state = {
a : 10
}
}
render(){
return (
<div>
<Fun />
</div>
);
}
}
现在应用还在测试阶段,但 React 官方说了一句话,Hooks 将成为 React 官方推荐的写法!
六、路由和 redux 支持 hooks 版本
- React Redux 从 v7.1.0 开始支持 Hook API 并暴露了 useDispatch 和 useSelector 等 hook。使用教程[译] React-Redux 官方 Hooks 文档说明
- React Router 从 v5.1 开始支持 hook 并暴露了 useHistory 、useLocation 、 useParams 等 hook。
- 推荐教程:React Hooks 入门教程