hooks组件排列
先来举个反例,未做优化的写法
import React, { useState } from 'react';
const Header = ({ title }) => {
console.log('header render')
return (
<div>
{title}
</div>
)
}
const Footer = () => {
console.log('footer render')
return (
<div>
页底
</div>
)
}
export default function Home() {
const [num, setNum] = useState(0);
console.log('render')
return (
<div>
<Header title={'标题头部'} />
<div>
<span>{num}</span>
<button onClick={() => setNum(v => v + 1)}>点击</button>
</div>
<Footer />
</div>
);
}
这里当点击按钮以后会引起num变化从而重新渲染header和footer两个子组件,造成多余的render性能损耗。
正确写法之一(将组件通过memo或者useMemo进行包裹)
import React, { useState, useMemo, memo } from 'react';
const Footer = memo(() => {
console.log('footer render')
return (
<div>
页底
</div>
)
})
const Head = ({ title }) => {
console.log('header render')
return (
<div>
{title}
</div>
)
}
export default function Home() {
const [num, setNum] = useState(0);
console.log('render')
const Header = useMemo(() => <Head title={'标题头部'} />, [])
return (
<div>
{Header}
<div>
<span>{num}</span>
<button onClick={() => setNum(v => v + 1)}>点击</button>
</div>
<Footer />
</div>
);
}
我们都知道react的组件会根据 state和props属性(父组件传递的) 的变化而发生重新re-render渲染,这里用memo或者useMemo包裹以后,会自动帮我们做一个浅比较,避免造成不必要的渲染。
正确写法之二(思想都是互通的,就是尽可能减少组件的不必要渲染而少做事,都拆分成独立子组件)
import React, { useState } from 'react';
const Header = ({ text }) => {
console.log('header render')
return (
<div>
{text}
</div>
)
}
const Footer = () => {
console.log('footer render')
return (
<div>
页底
</div>
)
}
const Content = () => {
const [num, setNum] = useState(0);
console.log('content render')
return (
<div>
<span>{num}</span>
<button onClick={() => setNum(v => v + 1)}>点击</button>
</div>
)
}
export default function Home() {
console.log('render')
return (
<div>
<Header text={'标题头部'} />
<Content />
<Footer />
</div>
);
}
合理使用useReducer去替代useState
有时候我们会遇到一个场景,在一个函数里需要setState两到三次,但是Hooks中的setState 与 Class 中最大区别在于Hooks的setState会覆盖原本的值,所以需要用扩展符
其中一个解决方案是合并state操作,如以下
//伪代码
const [param,setParam] = useState({
name:'',
age:''
})
const onChange = () => {
setParam(state => ({
...state,
name:'Alan',
age:20
}))
}
但是这样的做法不是万能解,当遇到要处理复杂逻辑时可以考虑用useReducer了,举个需要实现功能的例子: 一个简易多选框的实现,当其他选项被选中时,主选项要被选中,主选项可以控制其他选项是否被选中。
先来看下我的不规范写法
// 伪代码
const [checkBoxParam, setCheckBoxParam] = useState({
//主选项和次选项
primaryCheckBox: false,
secondCheckBox: []
})
// 所有checkbox的id
const allCheckBoxId = [1, 2, 3]
//选择次选项时
const onHandleClick = (checkBoxArray = []) => {
const { primaryCheckBox, secondCheckBox } = checkBoxParam;
//如果次选项选满的情况
if (checkBoxArray.length === allCheckBoxId.length) {
setCheckBoxParam(param => ({
...param,
primaryCheckBox: true,
secondCheckBox: allCheckBoxId
}))
} else {
setCheckBoxParam(param => ({
...param,
primaryCheckBox: false,
secondCheckBox: checkBoxArray
}))
}
}
//选择主选项时
const onPrimaryClick = (bool) => {
if (bool) {
setCheckBoxParam(param => ({
...param,
primaryCheckBox: true,
secondCheckBox: allCheckBoxId
}))
} else {
setCheckBoxParam(param => ({
...param,
primaryCheckBox: false,
secondCheckBox: []
}))
}
}
看了上面的代码,可以拆分出三种情况:
- 次选项未选满时
- 次选项被选满或者主选选项被选中
- 主选选择被反选
根据上面三种情况可以拆分成switch case的条件判断,由此来分析就可以用useReducer了
优化以后
//伪代码
const checkBoxReducer = (state, action) => {
switch (action.type) {
//次选项未选满
case 'second_check':
return { ...state, checkedPriceList: action.payload, primaryCheckBox: false };
//次选项被选满或者主选选项被选中
case 'all_check':
return { ...state, checkedPriceList: action.payload, primaryCheckBox: true };
//主选选择被反选
case 'un_all_check':
return { ...state, checkedPriceList: action.payload, primaryCheckBox: false };
default:
throw new Error();
}
}
const [checkBoxParam, dispatchCheckBoxParam] = useReducer(checkBoxReducer, {
//主选项和次选项
primaryCheckBox: false,
secondCheckBox: []
})
// 所有checkbox的id
const allCheckBoxId = [1, 2, 3]
//选择次选项时
const onHandleClick = (checkBoxArray = []) => {
//如果次选项选满的情况
if (checkBoxArray.length === allCheckBoxId.length) {
dispatchCheckBoxParam({ type: 'all_check', payload: allCheckBoxId });
} else {
dispatchCheckBoxParam({ type: 'second_check', payload: checkBoxArray });
}
}
//选择主选项时
const onPrimaryClick = (bool) => {
if (bool) {
dispatchCheckBoxParam({ type: 'all_check', payload: allCheckBoxId });
} else {
dispatchCheckBoxParam({ type: 'un_all_check', payload: [] });
}
}
使用useCallback缓存函数
useCallback主要用来作缓存函数,与useMemo的思想一样,根据依赖值的前后变化来判断函数是否被更新
function Parent(props) {
const [count, setCount] = useState(0);
// 使用 useCallback 缓存函数,当count对比前后发送变化时才改变函数
const addClick = useCallback(() => {
let sum = 0;
for (let i = 0; i < count; i++) {
sum += i;
}
return sum;
}, [count]);
const [value, setValue] = useState("");
return (
<div>
<h3>UseCallbackPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={value} onChange={event => setValue(event.target.value)} />
{/*将 使用 useCallback 缓存后的函数传递给子组件,可避免子组件不必要的更新*/}
<Child addClick={addClick} />
</div>
);
}
const Child = ({addClick}) => {
console.log("child render");
return (
<div>
<h3>Child</h3>
<button onClick={() => console.log(addClick())}>add</button>
</div>
);
}
在上面的代码中,使用 useCallback 将 addClick 函数做了缓存处理,然后把 addClick 作为props 传递给 Child 组件,当 Parent 组件更新时,就避免了 Child 组件的更新。
合理使用hooks组合思想(自定义校验表单)
先来说下使用场景需求,由于小程序的Form表单组件不好使用和修改,于是就直接写了input、checkbox等原始组件,但是这个时候涉及到多值的空校验和正则校验,处理不好的话代码就会写得比较杂乱。
import React, {useCallback,useState} from 'react';
import _isEmpty from 'lodash/isEmpty';
import _debounce from 'lodash/debounce';
//需要校检的选项列表
const validatorList = [
{
value: 'phone',
checkFunc: value => new RegExp(/^[1][3,5,7,8][0-9]{9}$/).test(value),
msg: '请填写正确格式的手机号码'
},
{
value: 'code',
checkFunc: value => !_isEmpty(value.trim()),
msg: '请填写验证码'
}
];
//input框防抖
const handleInput = _debounce((value, handle) => {
handle(value);
}, 500);
const LoginComponent = () => {
const [phone, setPhone] = useState();
const [code, setCode] = useState();
//收集表单的值
const getFormValue = useCallback(() => {
const data = {
phone, code
};
return data;
}, [phone, code])
//校验表单每项的值
const checkFormValue = useCallback(() => {
const formValue = getFormValue();
let checkStatus = true;
for (let index = 0; index < validatorList.length; index++) {
const rule = validatorList[index];
if (!rule.checkFunc(formValue[rule.value])) {
message.error(rule.msg);
checkStatus = false;
break;
}
}
return checkStatus;
}, [getFormValue])
//提交表单
const handleSubmit = useCallback(() => {
if (!checkFormValue()) {
return;
}
const data = getFormValue();
axios.post('/api', data)
.then(res => console.log('返回数据:' + res))
.catch(err => console.log('返回数据失败: ' + err))
}, [checkFormValue, getFormValue])
return (
<div>
<input
name='phone'
value={phone}
onchange={v => handleInput(v, setPhone)}
/>
<input
name='code'
value={code}
onchange={v => handleInput(v, setCode)}
/>
<button onClick={handleSubmit}> 确定 </button>
</div>
)
}