背景
-
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 是最基本的一个 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
,无论更新或卸载
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 注意事项
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 和 DidUpdate
useEffect(() => {
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
是单个组件状态管理,组件通讯还需要props
-
redux
是全局的状态管理,多组件共享数据
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
内部不能修改state
-
useEffect
可能出现死循环
// 关于 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
内部不能修改state
-
useEffect
依赖引用类型,可能会出现死循环
React 18 新增 hooks?
useTransition
使用useTransition
来降低渲染优先级, 可以指定 UI
的渲染优先级,哪些需要实时更新,哪些需要延迟更新。
useDefferdValue
允许变量延时更新,接收一个可选的延迟更新的最长时间
userId
使用 useId
可以生成客户端与服务端之间的唯一id
,并且返回一个字符串。
useSyncExternalStore
可以使 React
在并发模式下,保持自身 state
和来自 redux
的状态同步。文章来源:https://www.toymoban.com/news/detail-809881.html
useInsertionEffect
useInsertionEffect
可以在布局副作用触发之前将元素插入到 DOM
中。文章来源地址https://www.toymoban.com/news/detail-809881.html
到了这里,关于React Hooks 基本使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!