背景
class组件如果业务复杂,很难拆分和重构,很难测试;相同业务逻辑分散到各个方法中,逻辑混乱- 逻辑复用像
HOC、Render Props,不易理解,学习成本高 React提倡函数式编程,函数更易拆分,更易测试- 但是函数组件太简单,为了增强函数组件的功能,媲美
class组件:- 函数组件没有
state和setState - 函数组件没有生命周期
- 函数组件没有
React Hooks 使用规范
- 只能用于
React函数组件和自定义Hook中,其他地方不可以 - 只能用于顶层代码,不能在循环、判断中使用
Hooks eslint插件eslint-plugin-react-hooks
基本使用
useState
- 可用于模拟
class组件的state和setState
import React, { useState } from 'react';function ClickCounter() {// 数组的解构// useState 是最基本的一个 Hookconst [count, setCount] = useState(0); // 传入一个初始值const [name, setName] = useState('章三');// const arr = useState(0);// const count = arr[0];// const setCount = arr[1];function clickHandler() {setCount(count + 1);setName(name + '2020');}return (<div><p>你点击了 {count} 次 {name}</p><button onClick={clickHandler}>点击</button></div>);
}export default ClickCounter;
useEffect 模拟生命周期
- 默认函数组件没有生命周期
- 函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期
- 使用
Effect hooks可以把生命周期“钩”到函数组件中
useEffect 中返回函数 fn:
useEffect依赖[],组件销毁是执行fn,等于componentWillUnmountuseEffect无依赖或依赖[a,b],组件更新时执行fn,即下一次执行useEffect之前,就会执行fn,无论更新或卸载
import React, { useState, useEffect } from 'react';function LifeCycles() {const [count, setCount] = useState(0);const [name, setName] = useState('章三');// // 模拟 class 组件的 DidMount 和 DidUpdate// useEffect(() => {// console.log('在此发送一个 ajax 请求');// });// // 模拟 class 组件的 DidMount// useEffect(() => {// console.log('加载完了');// }, []) // 第二个参数是 [] (不依赖于任何 state);// // 模拟 class 组件的 DidUpdate// useEffect(() => {// console.log('更新了');// }, [count, name]); // 第二个参数就是依赖的 state// 模拟 class 组件的 DidMountuseEffect(() => {let timerId = window.setInterval(() => {console.log(Date.now());}, 1000);// 返回一个函数// 模拟 WillUnMountreturn () => {window.clearInterval(timerId);};}, []);function clickHandler() {setCount(count + 1);setName(name + '2020');}return (<div><p>你点击了 {count} 次 {name}</p><button onClick={clickHandler}>点击</button></div>);
}export default LifeCycles;
模拟 componentWillUnMount 注意事项
import React from 'react';class FriendStatus extends React.Component {constructor(props) {super(props);this.state = {status: false // 默认当前不在线};}render() {return (<div>好友 {this.props.friendId} 在线状态:{this.state.status}</div>);}componentDidMount() {console.log(`开始监听 ${this.props.friendId} 的在线状态`);}componentWillUnmount() {console.log(`结束监听 ${this.props.friendId} 的在线状态`);}// friendId 更新componentDidUpdate(prevProps) {console.log(`结束监听 ${prevProps.friendId} 在线状态`);console.log(`开始监听 ${this.props.friendId} 在线状态`);}
}export default FriendStatus;
import React, { useState, useEffect } from 'react';function FriendStatus({ friendId }) {const [status, setStatus] = useState(false);// DidMount 和 DidUpdateuseEffect(() => {console.log(`开始监听 ${friendId} 在线状态`);// 【特别注意】// 此处并不完全等同于 componentWillUnMount// props 发生变化,即更新,也会执行结束监听// 准确的说:返回的函数,会在下一次 effect 执行之前,被执行return () => {console.log(`结束监听 ${friendId} 在线状态`);};});return (<div>好友 {friendId} 在线状态:{status.toString()}</div>);
}export default FriendStatus;
useRef
- 获取
dom节点
import React, { useRef, useEffect } from 'react';function UseRef() {const btnRef = useRef(null); // 初始值// const numRef = useRef(0);// numRef.current;useEffect(() => {console.log(btnRef.current); // DOM 节点}, []);return (<div><button ref={btnRef}>click</button></div>);
}export default UseRef;
useContext
- 定义一个主题,可隔层传递
import React, { useContext } from 'react';// 主题颜色
const themes = {light: {foreground: '#000',background: '#eee'},dark: {foreground: '#fff',background: '#222'}
};// 创建 Context
const ThemeContext = React.createContext(themes.light); // 初始值function ThemeButton() {const theme = useContext(ThemeContext);return (<button style={{ background: theme.background, color: theme.foreground }}>hello world</button>);
}function Toolbar() {return (<div><ThemeButton></ThemeButton></div>);
}function App() {return (<ThemeContext.Provider value={themes.dark}><Toolbar></Toolbar></ThemeContext.Provider>);
}export default App;
useReducer
useReducer 和 redux 的区别:
useReducer是useState的代替方案,用于state复杂变化useReducer是单个组件状态管理,组件通讯还需要propsredux是全局的状态管理,多组件共享数据
import React, { useReducer } from 'react';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:return state;}
};function App() {// 很像 const [count, setCount] = useState(0)const [state, dispatch] = useReducer(reducer, initialState);return (<div>count: {state.count}<button onClick={() => dispatch({ type: 'increment' })}>increment</button><button onClick={() => dispatch({ type: 'decrement' })}>decrement</button></div>);
}export default App;
useMemo
React默认会更新所有子组件class组件使用SCU和PureComponent做优化Hooks中使用useMemo,但优化的原理是相同的
使用 useMemo做性能优化:
useMemo缓存值- 依赖不变,
useMemo会取上一次缓存的值,一般用于避免复杂计算。
import React, { useState, memo, useMemo } from 'react';// 子组件
// function Child({ userInfo }) {
// console.log('Child render...', userInfo);// return (<div>
// <p>This is Child {userInfo.name} {userInfo.age}</p>
// </div>);
// }// 类似 class PureComponent ,对 props 进行浅层比较
const Child = memo(({ userInfo }) => {console.log('Child render...', userInfo);return (<div><p>This is Child {userInfo.name} {userInfo.age}</p></div>);
});// 父组件
function App() {console.log('Parent render...');const [count, setCount] = useState(0);const [name, setName] = useState('章三');// const userInfo = { name, age: 20 }// 用 useMemo 缓存数据,有依赖const userInfo = useMemo(() => {return { name, age: 21 };}, [name]);return (<div><p>count is {count}<button onClick={() => setCount(count + 1)}>click</button></p><Child userInfo={userInfo}></Child></div>);
}export default App;
useCallback
使用 useCallback做性能优化:
useCallback缓存函数- 依赖不变,
useCallback会取上一次缓存的函数,一般用于父组件给子组件传递函数,减少子组件的渲染次数。
import React, { useState, memo, useMemo, useCallback } from 'react';// 子组件,memo 相当于 PureComponent
const Child = memo(({ userInfo, onChange }) => {console.log('Child render...', userInfo);return (<div><p>This is Child {userInfo.name} {userInfo.age}</p><input onChange={onChange}></input></div>);
});// 父组件
function App() {console.log('Parent render...');const [count, setCount] = useState(0);const [name, setName] = useState('章三');// 用 useMemo 缓存数据const userInfo = useMemo(() => {return { name, age: 21 };}, [name]);// function onChange(e) {// console.log(e.target.value);// }// 用 useCallback 缓存函数const onChange = useCallback(e => {console.log(e.target.value);}, []);return (<div><p>count is {count}<button onClick={() => setCount(count + 1)}>click</button></p><Child userInfo={userInfo} onChange={onChange}></Child></div>);
}export default App;
自定义 hooks
作用:
- 封装通用的功能
- 开发和使用第三方
Hooks - 自定义
Hook带来了无限的扩展性,解耦代码
使用:
- 本质是一个函数,以
use开头 - 内部正常使用
useState、useEffect获取其他Hooks - 自定义返回结果,格式不限
import { useState, useEffect } from 'react';function useMousePosition() {const [x, setX] = useState(0);const [y, setY] = useState(0);useEffect(() => {function mouseMoveHandler(event) {setX(event.clientX);setY(event.clientY);}// 绑定事件document.body.addEventListener('mousemove', mouseMoveHandler);// 解绑事件return () => document.body.removeEventListener('mousemove', mouseMoveHandler);}, []);return [x, y];
}export default useMousePosition;
import { useState, useEffect } from 'react';
import axios from 'axios';// 封装 axios 发送网络请求的自定义 Hook
function useAxios(url) {const [loading, setLoading] = useState(false);const [data, setData] = useState();const [error, setError] = useState();useEffect(() => {// 利用 axios 发送网络请求setLoading(true);axios.get(url) // 发送一个 get 请求.then(res => setData(res)).catch(err => setError(err)).finally(() => setLoading(false));}, [url]);return [loading, data, error];
}export default useAxios;// 第三方 Hook
// https://nikgraf.github.io/react-hooks/
// https://github.com/umijs/hooks
hooks 注意事项
useState初始化值只有第一次有效useEffect内部不能修改stateuseEffect可能出现死循环
// 关于 useEffect 内部不能修改 state 的问题
import { useEffect, useState } from 'react';const HelloWorld = () => {const [count, setCount] = useState(0);useEffect(() => {const timer = setInterval(() => {setCount(pre => pre + 1);}, 1000);return () => clearInterval(timer);}, []);return (<p>{count}</p>);
}export default HelloWorld;
为何 hooks 要依赖于调用顺序?
import React, { useState, useEffect } from 'react';function Teach({ couseName }) {// 函数组件,纯函数,执行完即销毁// 所以,无论组件初始化(render)还是组件更新(re-render)// 都会重新执行一次这个函数,获取最新的组件// 这一点和 class 组件不一样// render: 初始化 state 的值 '张三'// re-render: 读取 state 的值 '张三'const [studentName, setStudentName] = useState('张三');// if (couseName) {// const [studentName, setStudentName] = useState('张三');// }// render: 初始化 state 的值 '里斯'// re-render: 读取 state 的值 '里斯'const [teacherName, setTeacherName] = useState('里斯')// if (couseName) {// useEffect(() => {// // 模拟学生签到// localStorage.setItem('name', studentName);// });// }// render: 添加 effect 函数// re-render: 替换 effect 函数(内部的函数也会重新定义)useEffect(() => {// 模拟学生签到localStorage.setItem('name', studentName);});// render: 添加 effect 函数// re-render: 替换 effect 函数(内部的函数也会重新定义)useEffect(() => {// 模拟开始上课console.log(`${teacherName} 开始上课,学生 ${studentName}`);});return (<div>课程:{couseName},讲师:{teacherName},学生:{studentName}</div>);
}export default Teach;
FQA
- 为什么会有
React hooks?
class组件不易拆分、不易测试、业务代码分散在各个方法中- 函数组件更易拆分和测试;但是函数组件没有state和生命周期,所以需要使用
hooks来增强函数组件的功能
React hooks如何模拟生命周期?
- 模拟
componentDidMount-useEffect依赖[] - 模拟
componentDidUpdate-useEffect无依赖,或者依赖[a,b] - 模拟
componentWillUnMount-useEffect依赖[],并返回一个函数
- 如何自定义
hooks?
- 以
use开头,定义一个函数 - 内部正常使用
useState、useEffect获取其他Hooks - 自定义返回结果,格式不限
- 为什么要使用
hooks?
- 完善函数组件的能力,函数更适合
React组件 - 组件逻辑复用,
hooks表现更好
hooks组件逻辑复用的好处?
HOC组件层级嵌套过多,不易渲染,不易调试;HOC会劫持props,必须严格规范,容易出现疏漏Render Props学习成本高,不易理解;只能传递纯函数,而默认情况下纯函数功能有限hooks做组件逻辑复用,完全符合hooks原有原则,没有其他要求,易理解记忆;变量作用域明确;不会产生组件嵌套
React hooks容易遇到哪些坑?
useState初始化值,只能初始化一次useEffect内部不能修改stateuseEffect依赖引用类型,可能会出现死循环
React 18 新增 hooks?
useTransition
使用useTransition来降低渲染优先级, 可以指定 UI 的渲染优先级,哪些需要实时更新,哪些需要延迟更新。
useDefferdValue
允许变量延时更新,接收一个可选的延迟更新的最长时间
userId
使用 useId 可以生成客户端与服务端之间的唯一id ,并且返回一个字符串。
useSyncExternalStore
可以使 React 在并发模式下,保持自身 state 和来自 redux 的状态同步。
useInsertionEffect
useInsertionEffect 可以在布局副作用触发之前将元素插入到 DOM 中。