1、forceUpdate
在Class组件中使用this.forceUpdate()可以直接强制渲染,已最新的数据执行一次render,但是hooks是函数组件,没有当前组件的this指针,所以实现方式也有所区别
import React, { useReducer } from 'react'
export default function () {
const [, forceUpdate] = useReducer(v => v + 1, 0)
}
使用useReducer,调用一次来触发数据更新
2、组件首次不执行
如果组件订阅了props中的某个值,然后来执行一些操作,但是我们首次不需要执行,这种情况的话使用useRef
import React, { useRef } from 'react'
export default function (props) {
const firstUpdate = useRef(true)
useEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
return;
}
// 执行内容
}, [props])
}
3、对比上次的props,如果更改才执行
在类组件componentWillReceiveProps的钩子中,能拿到上次的props和最新的props,而hooks中没有这个钩子,那我们需要对比两次的props怎么办呢?还是用useRef
import React, { useRef } from 'react'
import { isEqual } from 'lodash';
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
export default function (props) {
const preProps = usePrevious(props);
useEffect(() => {
if (isEqual(preProps, props)) {
return;
}
// 执行内容
}, [props])
}
4、复用的组件,申明的变量需要放在函数内
- 放在函数外,如下:
import React, { useReducer } from 'react'
let catchRecord = {}
export default function () {
}
如果要将这个变量缓存其他函数中的某个值,那么这个组件复用成多个组件实例之后,就会冲突,某个组件的缓存值会把其他组件的缓存值覆盖,一句话,就是他们都用的一个变量了。
- 正确的放在函数类
import React from 'react'
export default function () {
let catchRecord = {}
}
5、更新值的异步问题
class组件更新值,可以使用函数作为this.setState的参数来实现同步,也能在第二个参数作为回调函数中来执行。hooks中更新值后,立刻要以新的值来执行的话,可以采用以下几种:
- 1、useEffect
import React, { useState } from 'react'
export default function () {
const [name, setName] = useState('mySkey')
useEffect(() => {
// 执行内容
}, [name])
}
- 2、在执行函数以参数传入新的值
import React, { useState } from 'react'
export default function () {
const [name, setName] = useState('mySkey')
function changeName(newName) {
setName(newName)
saveName(newName)
}
function saveName(name) {
}
}
6、订阅了很多数据都要来执行一种操作
比如获取列表的接口请求,我们在useEffect中订阅了很多种数据更新都要请求,那么会导致一次发多个api请求,这时候需要把请求防抖处理
import React from 'react'
export default function () {
let timer;
function query() {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
}, 500)
}
}
7、当一个state依赖另一个state时
比如表格的columns是动态的,并且里面有的数据依赖另一个state时,那么在另一个state更新时,必须重新setColumns,否则将无法生效,如下
import React, { useState, useEffect } from 'react'
import { Table } from 'antd'
function createColumns(types) {
return [
{
title: '标题',
dataIndex: 'title'
},
{
title: '分类',
dataIndex: 'type',
render: (text) => types[text]
},
]
}
export default function () {
const [typesObj, setTypesObj] = useState({}])
const [columns, setColumns] = useState(createColumns(typesObj))
const [dataSource, setDataSource] = useState([])
useEffect(() => {
// 模拟延迟获取分类信息
setTimeout(() => {
setTypesObj({ joker: '笑话' })
}, 1000)
}, [])
return <Table columns={columns} dataSource={dataSource} />
}
上面代码,初始状态typesObj是个空对象,那么就在createColumns传入的是个{},如果不在后续更新了typesObj之后同步更新columns的话,就无法正常显示,所以,正确做法如下:
import React, { useState, useEffect } from 'react'
import { Table } from 'antd'
function createColumns(types) {
return [
{
title: '标题',
dataIndex: 'title'
},
{
title: '分类',
dataIndex: 'type',
render: (text) => types[text]
},
]
}
export default function () {
const [typesObj, setTypesObj] = useState({}])
const [columns, setColumns] = useState(createColumns(typesObj))
const [dataSource, setDataSource] = useState([])
useEffect(() => {
// 模拟延迟获取分类信息
setTimeout(() => {
setTypesObj({ joker: '笑话' })
}, 1000)
}, [])
useEffect(() => {
// 同步更新columns
setColumns(createColumns(typesObj))
}, [typesObj])
return <Table columns={columns} dataSource={dataSource} />
}
- 方法二:
columns使用useMemo,不使用useState来创建
import React, { useState, useEffect, useMemo } from 'react'
import { Table } from 'antd'
function createColumns(types) {
return [
{
title: '标题',
dataIndex: 'title'
},
{
title: '分类',
dataIndex: 'type',
render: (text) => types[text]
},
]
}
export default function () {
const [typesObj, setTypesObj] = useState({}])
const [dataSource, setDataSource] = useState([])
const columns = useMemo(() => createColumns(typesObj), [typesObj])
useEffect(() => {
// 模拟延迟获取分类信息
setTimeout(() => {
setTypesObj({ joker: '笑话' })
}, 1000)
}, [])
return <Table columns={columns} dataSource={dataSource} />
}
8、使用useCallback和useMemo提升组件性能
- 1、将与props和state无关的函数,放在外部申明
import React, { useState } from 'react'
function formatCounter(counterVal) {
return `The counter value is ${counterVal}`;
}
function App() {
const [counter, setCounter] = useState(0);
return (
<div className="App">
<div>{formatCounter(counter)}</div>
<button onClick={()=>onClick(setCounter)}>
Increment
</button>
</div>
);
}
- 2、useCallback
有些时候我们必须要在hooks组件内定义函数或者方法,那么推荐用useCallback缓存这个方法,当useCallback的依赖项不发生变化的时候,该函数在每次渲染的过程中不需要重新声明
useCallback接受两个参数,第一个参数是要缓存的函数,第二个参数是一个数组,表示依赖项,当依赖项改变的时候会去重新声明一个新的函数,否则就返回这个被缓存的函数.
import React, { useState, useCallback } from 'react'
function App(props) {
const [counter, setCounter] = useState(0);
const onClick = useCallback(()=>{
setCounter(props.count)
},[props.count]);
return (
<div className="App">
<div>{counter}</div>
<button onClick={onClick}>
Increment
</button>
</div>
);
}
-
3、useMemo
useMemo与useCallback大同小异,区别就是useMemo缓存的不是函数,缓存的是对象(可以是jsx虚拟dom对象),同样的当依赖项不变的时候就返回这个被缓存的对象,否则就重新生成一个新的对象。
import React, { useMemo } from 'react'
function App(props) {
const [counter, setCounter] = useState(0);
const countLabel = useMemo(()=>{
return `${counter}-label`
},[counter]);
return (
<div className="App">
<div>{counter}</div>
<div>{countLabel}</div>
</div>
);
}
9、useContext
和class组件的Context一致,用于多层组件传值
function useCounter() {
let [count, setCount] = useState(0)
let decrement = () => setCount(count - 1)
let increment = () => setCount(count + 1)
return { count, decrement, increment }
}
let Counter = createContext(null)
function CounterDisplay() {
let counter = useContext(Counter)
return (
<div>
<button onClick={counter.decrement}>-</button>
<p>You clicked {counter.count} times</p>
<button onClick={counter.increment}>+</button>
</div>
)
}
function App() {
let counter = useCounter()
return (
<Counter.Provider value={counter}>
<CounterDisplay />
<CounterDisplay />
</Counter.Provider>
)
}
10、useImperativeHandle
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用。
我在项目中只用到了一次,主要是给父组件暴露自定义的方法,而不是一股脑的直接返回ref,房子父组件操作ref导致问题
const Video = ({ ... }: PropTypes, ref: any) => {
const videoRef: any = useRef(null);
const playVideo = () => {...};
const handlePlayBtnClick = () => {...};
useImperativeHandle(ref, () => ({
play: playVideo,
pause: () => {
if (videoRef.current) {
videoRef.current.pause();
setVideoVisible(false);
}
},
}));
return (
<div className={styles.container}>
<video ref={videoRef}>
<source src={videoUrl} type="video/mp4" />
</video>
</div>
);
};
export default React.memo(React.forwardRef(Video));
11、定时器hook
每隔一段时间执行,组件卸载时清除
export const useInterval = (callback: () => void, delay: number) => {
const saveCallback = useRef(callback);
useEffect(() => {
saveCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay !== null) {
const timer = setInterval(() => saveCallback.current(), delay);
return () => {
if (timer) {
clearInterval(timer); // 取消定时器;
}
};
}
return () => { };
}, [delay]);
};
12、更新数据后回调拿到最新值
由于setState是异步的,我们需要在设置成功后拿到最新值
export function useSyncState(state) {
const cbRef: { current: any } = useRef();
const [data, setData] = useState(state);
useEffect(() => {
if (cbRef.current) cbRef.current(data);
}, [data]);
return [
data,
(val: any, callback: any) => {
cbRef.current = callback;
setData(val);
},
];
}