欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

使用react-hooks及在项目中使用react-hooks遇到的一些问题的解决

程序员文章站 2022-05-30 17:44:00
...

什么是Hook?
Hook是React16.8的新增特性。它可以让我们在不编写class的情况下使用state以及其他react的特性。
Hook 是一些可以让我们在函数组件里“钩入” React state 及生命周期等特性的函数。他可以让我们不使用 class 也能使用 React。

先看一下类组件和函数式组件在实现同一个功能时,两者的区别。

class组件

import React, { Component } from “react”;

export default class Button extends Component {
constructor() {
super();
this.state = { buttonText: “Click me, please” };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(() => {
return { buttonText: “Thanks, been clicked!” };
});
}
render() {
const { buttonText } = this.state;
return {buttonText};
}
}
hooks函数式组件

import React, { useState } from “react”; // 引入useState

export default function Button() {
const [buttonText, setButtonText] = useState(“Click me, please”); // 调用 useState 声明了一个新的 state 变量。它返回一对值给到我们命名的变量上。
// const [loading, setLoading] = useState(false) 如果需要多个变量,可以通过调用多次useState来声明
function handleClick() {
return setButtonText(“Thanks, been clicked!”); // 用户点击按钮后,我们传递一个新的值给 setCount。React 会重新渲染 Button 组件,并把最新的 值 传给它。
}

return {buttonText};
}
对比两种组件,以看出class组件的代码相对于函数式组件显得很笨重,层级很多,如果再加入redux的话就会变得更加复杂。

hooks常用的钩子:
1.状态钩子 useState
2.副作用钩子 useEffect
3. 共享状态钩子 useContext (我没用过。。。)
4. useCallback和useMemo
5.useRef

useState:
用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。

(1)useState()这个函数接受状态的初始值,作为参数,上例的初始值为按钮的文字。该函数返回一个数组,数组的第一个成员是一个变量(上例是buttonText),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是set前缀加上状态的变量名(上例是setButtonText)。

(2)如果需要多个变量,可以通过调用多次useState来声明。

(3)需要注意,useState是异步的 ,如果需要处理异步,可以使用useEffect来监听state是否发生变化,从而进行操作。(具体事例见后useEffect)。

useEffect:
用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()。

useEffect(() => {
const params = {
regionId: regionId || -1
}
http
.get(urls.API + ‘/QueryCondition/AuthCity/ByRegion’, params)
.then((res) => {
const { isSuccess, message, result } = res.data
if (!isSuccess) {
Message.error(message)
return
}
const dataList = props.noAll ? [] : [{ value: -1, label: ‘全部’ }]
if (result !== null) {
result.map((item) => {
if (item.name !== null) {
dataList.push({ value: item.id, label: item.name })
}
return item
})
}
setList(dataList || [])
setInitialValue(77 || null)
})
}, [regionId, props.noAll]) // 如果不传,每次组件渲染都会执行该useEffect
(1)上面用法中,useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()。
上例是我调用的一个获取城市的接口的副作用钩子,当大区变化时,就会触发该副作用。

(2)解决异步获取数据的问题

useEffect(() => {
if (checkedValue !== null || inputValue.length > 1) {
getSearchList()
}
}, [checkedValue, getSearchList, inputValue])
这里是我项目中写的一段通过查询条件获取查询列表的代码,直接获取checkedValue(单选框的值)和inputValue(输入框的值)当做参数给到接口是没有用的,我通过副作用hook来判断,当两者任意一个刷新时,执行查询方法。

useMemo:
使用hook时,react不再区分mount和update两个状态,这意味着函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。这里我们使用useCallback和useMemo可以优化性能问题。

(1)useCallback和useMemo的参数跟useEffect一致。
(2)useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。
反例:

import React from ‘react’;

export default function WithoutMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState(’’);

function expensive() {
    console.log('compute');
    let sum = 0;
    for (let i = 0; i < count * 100; i++) {
        sum += i;
    }
    return sum;
}

return <div>
    <h4>{count}-{val}-{expensive()}</h4>
    <div>
        <button onClick={() => setCount(count + 1)}>+c1</button>
        <input value={val} onChange={event => setValue(event.target.value)}/>
    </div>
</div>;

}
这里创建了两个state,然后通过expensive函数,执行一次昂贵的计算,拿到count对应的某个值。我们可以看到:无论是修改count还是val,由于组件的重新渲染,都会触发expensive的执行(能够在控制台看到,即使修改val,也会打印);但是这里的计算只依赖于count的值,在val修改的时候,是没有必要再次计算的。

export default function WithMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState(’’);
const expensive = useMemo(() => {
console.log(‘compute’);
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);

return <div>
    <h4>{count}-{expensive}</h4>
    {val}
    <div>
        <button onClick={() => setCount(count + 1)}>+c1</button>
        <input value={val} onChange={event => setValue(event.target.value)}/>
    </div>
</div>;

}
上面我们可以看到,使用useMemo来执行计算,然后将计算值返回,并且将count作为依赖值传递进去。这样,就只会在count改变的时候触发expensive执行,在修改val的时候,返回上一次缓存的值。

useCallBack:
(1)useCallback跟useMemo比较类似,但它返回的是缓存的函数。
(2)使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

遗留问题: 由于useEffect、useMemo、useCallback都是自带闭包的。每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,不像class组件中,有componentDidUpdate(),我们是无法使用它们来捕获上一次的状态。(不像class组件中,有componentDidUpdate())

注意:useEffect,useMemo,useCallback都有一个坑点,他们的第二个参数,就是那个用来放涉及的变量的数组,有的变量是不可以放在这个数组中的,一旦加了就会造成死循环。但是你不加上,eslint就会警告(React Hook useEffect has missing dependencies: ‘mockData’, and ‘targetKeys’. Either include them or remove the dependency array),逼死强迫症。我这边的处理方式如下:

const getSearchList = useCallback(
(): void => {
const params = {
name: inputValue.length > 1 ? inputValue : undefined,
type: checkedValue
}
const oldMockData = mockData.filter((item) => {
for (let i = 0; i < targetKeys.length; i++) {
if (item.key === targetKeys[i]) {
return item
}
}
})
http
.get(urls.CARAPI + ‘/CarRepair/RepairItem’, params)
.then((res: any) => {
const { isSuccess, message, result } = res.data
const data = []
if (!isSuccess) {
Message.error(message)
return
}
result.map((item) => {
data.push({
value: item.id,
label: item.name,
key: item.id,
count: 1,
material: 0,
hourFee: 0,
serviceTypeName: item.serviceTypeName,
serviceTypeId: item.serviceTypeId
})
return item
})
setMockData([…data, …oldMockData])
})
},
// eslint-disable-next-line
[checkedValue, inputValue]
)
用 //eslint-disable-next-line 让eslint忽略下一行的校验,暂时只找到这个方法,有其他的方法可以教教我。

useRef:
react hooks 用来获取组件实例对象或者是DOM对象的钩子。
在我们实际开发中,经常会遇到父组件想要获取子组件的实例上的某些变量或者方法。class组件提供了createRef,useRef则是提供给函数式组件的钩子。
父组件片段:

const OutRepairAdd: React.FC = () => {
const tranRef: any = useRef()
const [form] = Form.useForm()
const [loading, setLoading] = useState(false)
const [carInfo, setCarInfo] = useState([] as carInfoParams)
const [imageList, setImageList] = useState([])
const [showBtn, setShowBtn] = useState(false)

// 获取子组件ref上的参数
const box = tranRef.current.checkedBox

handleAdd = () => {
this.setState({
applyLoading: true
})
applyRef.current.handleApply()
setTimeout(() => {
this.afterQuery()
}, 1500)
}

// 子组件
子组件片段:

interface transferProps {
[propName: string]: any
}
const TableTransfer = forwardRef((props: transferProps, ref: any) => {
const [checkedBox, setCheckedBox] = useState([])
const [checkedValue, setCheckedValue] = useState(null)

useImperativeHandle(ref, () => ({
checkedBox,
// handleApply 是暴露给父组件的提交方法
handleApply: async () => {
const data = {
id: props.id,
status:
radioValue === 1
? new Big(approvalStatus).plus(1).toNumber()
: radioValue,
oprNo: String(userId),
remark: remarkValue,
orderItems: approvalStatus === 2 ? tableList : null
}
await http
.put(urls.CARAPI + ‘/CarRepairOuter/Approval’, ‘’, data)
.then((res) => {
const { isSuccess, message, result } = res.data
if (!isSuccess) {
Message.error(message)
props.closeLoading()
return
}
if (!result) {
Message.error(message)
props.closeLoading()
}
props.closeLoading()
props.onCancel()
Message.success(‘审批完成’)
})
}
}))
(1)上例中,子组件引用了一个useImperativeHandle方法,useImperativeHandle 可以让我们在使用 ref 时自定义暴露给父组件的实例值
(2)useImperativeHandle 应当与 forwardRef 一起使用。

useContext:
如果需要在组件之间共享状态,可以使用useContext()。

相关标签: JavaScript笔记