背景
class
组件如果业务复杂,很难拆分和重构,很难测试;相同业务逻辑分散到各个方法中,逻辑混乱- 逻辑复用像
HOC
、Render Props
,不易理解,学习成本高 React
提倡函数式编程,函数更易拆分,更易测试- 但是函数组件太简单,为了增强函数组件的功能,媲美
class
组件:- 函数组件没有
state
和setState
- 函数组件没有生命周期
- 函数组件没有
React Hooks 使用规范
- 只能用于
React
函数组件和自定义Hook
中,其他地方不可以 - 只能用于顶层代码,不能在循环、判断中使用
Hooks
eslint
插件eslint-plugin-react-hooks
基本使用
useState
- 可用于模拟
class
组件的state
和setState
javascript
import React, { useState } from 'react';
function ClickCounter() {
// 数组的解构
// useState 是最基本的一个 Hook
const [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
,等于componentWillUnmount
useEffect
无依赖或依赖[a,b]
,组件更新时执行fn
,即下一次执行useEffect
之前,就会执行fn
,无论更新或卸载
javascript
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 组件的 DidMount
useEffect(() => {
let timerId = window.setInterval(() => {
console.log(Date.now());
}, 1000);
// 返回一个函数
// 模拟 WillUnMount
return () => {
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 注意事项
javascript
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;
javascript
import React, { useState, useEffect } from 'react';
function FriendStatus({ friendId }) {
const [status, setStatus] = useState(false);
// DidMount 和 DidUpdate
useEffect(() => {
console.log(`开始监听 ${friendId} 在线状态`);
// 【特别注意】
// 此处并不完全等同于 componentWillUnMount
// props 发生变化,即更新,也会执行结束监听
// 准确的说:返回的函数,会在下一次 effect 执行之前,被执行
return () => {
console.log(`结束监听 ${friendId} 在线状态`);
};
});
return (<div>
好友 {friendId} 在线状态:{status.toString()}
</div>);
}
export default FriendStatus;
useRef
- 获取
dom
节点
javascript
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
- 定义一个主题,可隔层传递
javascript
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
是单个组件状态管理,组件通讯还需要props
redux
是全局的状态管理,多组件共享数据
javascript
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
会取上一次缓存的值,一般用于避免复杂计算。
javascript
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
会取上一次缓存的函数,一般用于父组件给子组件传递函数,减少子组件的渲染次数。
javascript
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
- 自定义返回结果,格式不限
javascript
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;
javascript
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
内部不能修改state
useEffect
可能出现死循环
javascript
// 关于 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
要依赖于调用顺序?
javascript
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
内部不能修改state
useEffect
依赖引用类型,可能会出现死循环
React 18 新增 hooks?
useTransition
使用useTransition
来降低渲染优先级, 可以指定 UI
的渲染优先级,哪些需要实时更新,哪些需要延迟更新。
useDefferdValue
允许变量延时更新,接收一个可选的延迟更新的最长时间
userId
使用 useId
可以生成客户端与服务端之间的唯一id
,并且返回一个字符串。
useSyncExternalStore
可以使 React
在并发模式下,保持自身 state
和来自 redux
的状态同步。
useInsertionEffect
useInsertionEffect
可以在布局副作用触发之前将元素插入到 DOM
中。