乐趣区

React hooks实践

前言
最近要对旧的项目进行重构,统一使用全新的 react 技术栈。同时,我们也决定尝试使用 React hooks 来进行开发,但是,由于 React hooks 崇尚的是使用(也只能使用)function component 的形式来进行开发,而不是 class component,因此,整个开发方式也会与之前产生比较大的差异。所以,我这里就积累了下实际项目中遇到的问题以及思考,看下能不能帮助大家少走弯路。
正文
接下来就直接进入正文。我会将项目中遇到的问题一一列举出来,并且给出解决方案。
执行初始化操作的时机
当我转到 React hooks 的时候,首先就遇到了这个问题:
一般来说,业务组件经常会遇到要通过发起 ajax 请求来获取业务数据并且执行初始化操作的场景。在使用 class component 编程的时候,我们就可以在 class component 提供的生命周期钩子函数 (比如 componentDidMount, constructor 等) 执行这个操作。可是如果转到 React hooks 之后,function component 里是没有这个生命周期钩子函数的,那这个初始化操作怎么办呢?总不能每次遇到这种场景都使用 class component 来做吧?
解决方案:使用 useEffect(想知道 useEffect 是什么的话,可以点击这里)
useEffect,顾名思义,就是执行有副作用的操作,你可以把它当成 componentDidMount, componentDidUpdate, and componentWillUnmount 的集合。它的函数声明如下
useEffect(effect: React.EffectCallback, inputs?: ReadonlyArray<any> | undefined)
那么,我们在实际使用中,我们就可以使用这个来执行初始化操作。举个例子
import React, {useEffect} from ‘react’

export function BusinessComponent() {
const initData = async () => {
// 发起请求并执行初始化操作
}
// 执行初始化操作,需要注意的是,如果你只是想在渲染的时候初始化一次数据,那么第二个参数必须传空数组。
useEffect(() => {
initData();
}, []);

return (<div></div>);
}
需要注意的是,这里的 useEffect 的第二个参数必须传空数组,这样它就等价于只在 componentDidMount 的时候执行。如果不传第二个参数的话,它就等价于 componentDidMount 和 componentDidUpdate
做一些清理操作
由于我们在实际开发过程中,经常会遇到需要做一些副作用的场景,比如轮询操作 (定时器、轮询请求等)、使用浏览器原生的事件监听机制而不用 react 的事件机制(这种情况下,组件销毁的时候,需要用户主动去取消事件监听) 等。使用 class Component 编程的时候,我们一般都在 componentWillUnmount 或者 componentDidUnmount 的时候去做清理操作,可是使用 react hooks 的时候,我们如何做处理呢?
解决方案:使用 useEffect 第一个参数的返回值
如果 useEffect 的第一个参数返回了函数的时候,react 会在每一次执行新的 effects 之前,执行这个函数来做一些清理操作。因此,我们就可以使用它来执行一些清理操作。
例子:比如我们要做一个二维码组件,我们需要根据传入的 userId 不断轮询地向后台发请求查询扫描二维码的状态,这种情况下,我们就需要在组件 unmount 的时候清理掉轮询操作。代码如下:
import React, {useEffect} from ‘react’

export function QRCode(url, userId) {
// 根据 userId 查询扫描状态
const pollingQueryingStatus = async () => {
}
// 取消轮询
const stopPollingQueryStatus = async() => {
}

useEffect(() => {
pollingQueryingStatus();

return stopPollingQueryStatus;
}, []);

// 根据 url 生成二维码
return (<div></div>)
}
这样的话,就等价于在 componentWillUnmount 的时候去执行清理操作。
但是,有时候我们可能需要执行多次清理操作。还是举上面的例子,我们需要在用户传入新的 userId 的时候,去执行新的查询的操作,同时我们还需要清除掉旧的轮询操作。想一下怎么做比较好。
其实对这种情况,官方也已经给出了解决方案了,useEffect 的第二个参数是触发 effects 的关键,如果用户传入了第二个参数,那么只有在第二个参数的值发生变化 (以及首次渲染) 的时候,才会触发 effects。因此,我们只需要将上面的代码改一下:
import React, {useEffect} from ‘react’

export function QRCode(url, userId) {
// 根据 userId 查询扫描状态
const pollingQueryingStatus = async () => {
}

const stopPollingQueryStatus = async() => {
}
// 我们只是将 useEffect 的第二个参数加了个 userId
useEffect(() => {
pollingQueryingStatus();

return stopPollingQueryStatus;
}, [userId]);

// 根据 url 生成二维码
return (<div></div>)
}
我们只是在 useEffect 的第二个参数数组里,加入了一个 userId。这样的话,userId 的每一次变化都会先触发 stopPollingQueryStatus,之后再执行 effects,这样就可以达到我们的目的。
useState 与 setState 的差异
react hooks 使用 useState 来代替 class Component 里的 state。可是,在具体开发过程中,我也发现了一些不同点。useState 介绍可以点击这里
在 setState 的时候,我们可以只修改 state 中的局部变量,而不需要将整个修改后的 state 传进去,举个例子
import React, {PureComponent} from ‘react’;

export class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
name: ‘cjg’,
age: 18,
}
}

handleClick = () => {
const {count} = this.state;
// 我们只需要传入修改的局部变量
this.setState({
count: count + 1,
});
}

render() {
return (
<button onClick={this.handleClick}></button>
)
}
}
而使用 useState 后,我们修改 state 必须将整个修改后的 state 传入去,因为它会直接覆盖之前的 state,而不是合并之前 state 对象。
import React, {useState} from ‘react’;

export function Count() {
const [data, setData] = useState({
count: 0,
name: ‘cjg’,
age: 18,
});

const handleClick = () => {
const {count} = data;
// 这里必须将完整的 state 对象传进去
setData({
…data,
count: count + 1,
})
};

return (<button onClick={handleClick}></button>)
}
减少不必要的渲染
在使用 class Component 进行开发的时候,我们可以使用 shouldComponentUpdate 来减少不必要的渲染,那么在使用 react hooks 后,我们如何实现这样的功能呢?
解决方案:React.memo 和 useMemo
对于这种情况,react 当然也给出了官方的解决方案,就是使用 React.memo 和 useMemo。
React.memo
React.momo 其实并不是一个 hook,它其实等价于 PureComponent,但是它只会对比 props。使用方式如下(用上面的例子):
import React, {useState} from ‘react’;

export const Count = React.memo((props) => {
const [data, setData] = useState({
count: 0,
name: ‘cjg’,
age: 18,
});

const handleClick = () => {
const {count} = data;
setData({
…data,
count: count + 1,
})
};

return (<button onClick={handleClick}>count:{data.count}</button>)
});

useMemo
useMemo 它的用法其实跟 useEffects 有点像,我们直接看官方给的例子
function Parent({a, b}) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
从例子可以看出来,它的第二个参数和 useEffect 的第二个参数是一样的,只有在第二个参数数组的值发生变化时,才会触发子组件的更新。
总结
一开始在从 class component 转变到 react hooks 的时候,确实很不适应。可是当我习惯了这种写法后,我的心情如下:

当然,现在 react hooks 还是在 alpha 阶段,如果大家觉得不放心的话,可以再等等。反正我就先下手玩玩了哈哈哈。
本文地址在 -> 本人博客地址, 欢迎给个 start 或 follow

退出移动版