1. 在FunctionComponent组件中使用debounce
error:
const FC = () => {
// debounce函数
const fetchAddress = debounce(value => {
fetchApi(value).then(() => {
// do something
});
}, 700);
return <Input onChange={(e) => { fetchAddress(e.target.value) }} />
}
right:
const FC = () => {
// debounce函数
const fetchAddress = useCallback(debounce(value => {
fetchApi(value).then(() => {
// do something
});
}, 700), []);
return <Input onChange={(e) => { fetchAddress(e.target.value) }} />
}
需要用usecallback缓存起来,不然每次渲染后debounce都是一个新的函数,没有防抖的效果;
2. 每轮渲染,函数组件都拥有其独立的状态
example1:
function Example() {
const [count, setCount] = useState(0);
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count); // 输出的是?
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<button onClick={handleAlertClick}>
Show alert
</button>
</div>
);
}
可用useRef保存上一轮的状态,因为useRef会在每次渲染时返回同一个ref对象,类似于class组件的实例属性(this.xxx):
function Example() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + prevCountRef.current); // 输出的是?
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => {
setCount(count + 1)
prevCountRef.current = count
}}>
Click me
</button>
<button onClick={handleAlertClick}>
Show alert
</button>
</div>
);
}
example2:
const FC = ({ getAFn }) => {
const [a, setA] = useState(0);
const getA = () => a;
useEffect(() => {
getAFn(getA);
}, []);
return (
<p onClick={() => {
setA(a + 1);
}}>
click1
</p>
);
};
class A extends React.Component {
render() {
return (
<div>
<FC getAFn={(fn) => (this.fn = fn)} />
<span
onClick={() => {
console.log(this.fn()); // 输出多少?
}}
>
click2
</span>
</div>
);
}
}
export default A;
需要去掉useEffect的依赖,保证每轮渲染都抛出属于该轮的函数状态,下面是能得到正确结果的写法:
const FC = ({ getAFn }) => {
const [a, setA] = useState(0);
const getA = () => a;
useEffect(() => {
getAFn(getA);
});
return (
<p onClick={() => {
setA(a + 1);
}}>
click1
</p>
);
};
class A extends React.Component {
render() {
return (
<div>
<FC getAFn={(fn) => (this.fn = fn)} />
<span
onClick={() => {
console.log(this.fn()); // 输出多少?
}}
>
click2
</span>
</div>
);
}
}
export default A;
3.自定义Hook
1.处理antd table数据
自定义tablehook,脱离UI提取表格数据交互逻辑:
import { useReducer, useCallback, useEffect } from "react";
const defaultSearchParams = {};
// 抽取出表格可以共用的状态
const useTableHook = ({
queryApi, // 请求的接口
dataKey = "list", //返回数据列表的Key
totalKey = "total", // 返回数据总数的Key
pageIndexKey = "page", // 请求参数key
pageSizeKey = "size", // 请求参数key
searchParams = defaultSearchParams, //搜索参数,不能直接写searchParams = {}
callBack // 请求完成的回调
}) => {
const [tableState, dispatch] = useReducer((state, action) => {
const { type, payLoad } = action;
switch (type) {
case "setLoading":
return { ...state, loading: payLoad };
case "setPageIndex":
return { ...state, pageIndex: payLoad };
case "setPageSize":
return { ...state, pageSize: payLoad };
case "setTotal":
return { ...state, total: payLoad };
case "setList":
return { ...state, list: payLoad };
default:
return { state };
}
}, {
loading: false,
pageIndex: 1,
pageSize: 10,
total: 0,
list: []
});
const { pageIndex, pageSize, total } = tableState;
// 翻页配置
const pagination = {
current: pageIndex,
pageSize,
total,
onChange: pageIndex => {
dispatch({
type: "setPageIndex",
payLoad: pageIndex
});
},
onShowSizeChange: (page, pageSize) => {
dispatch({
type: "setPageIndex",
payLoad: 1
});
dispatch({
type: "setPageSize",
payLoad: pageSize
});
}
};
// 请求数据
const queryTable = useCallback(param => {
dispatch({ type: "setLoading", payLoad: true });
queryApi(param).then(({ data }) => {
if(data) {
dispatch({ type: "setList", payLoad: data[dataKey] || [] });
dispatch({ type: "setTotal", payLoad: data[totalKey] });
callBack && callBack();
}
}).finally(() => {
dispatch({ type: "setLoading", payLoad: false });
});
}, []);
useEffect(() => {
queryTable({
[pageIndexKey]: pageIndex, [pageSizeKey]: pageSize, ...searchParams
});
}, [pageIndex, pageSize, searchParams]);
return { tableState, dispatch, pagination, queryTable };
};
export default useTableHook;
在有表格的页面使用:
const TabelPage = () => {
const [params, setParams] = useState({});
const columns = [{
title: "模板ID",
dataIndex: "target",
align: "center",
}, ...];
const { tableState, dispatch, pagination } = useTableHook({
queryApi: api,
searchParams: params
});
const { loading, list } = tableState;
return (
<>
<Search doQuery={(params, type) => {
if(type === "reset") {
dispatch({ type: "setPageIndex", payLoad: 1});
dispatch({ type: "setPageSize", payLoad: 10});
}
setParams(params);
}} />
<Table
dataSource={list}
loading={loading}
columns={columns}
pagination={pagination}
/>
</>
);
};
这里我使用的是什么时候使用useReducer管理数据状态,那什么时候使用useState,什么时候使用useReducer呢?
- useState也是useReducer的封装,使用后的效果都是一样的;
- 当一个组件涉及的状态较多的时候,例如state是Object, Array类型,可以使用useReducer组织;
- 当state是一个简单的String, Number的时候,可使用useState;
- 如果每次修改,object的每个属性值都会一起改变,例如鼠标移动监听xy坐标{x, y},也可使用useState;
2.处理弹窗显示关闭交互逻辑
定义modalHook:
import React, { useState } from "react";
import { Modal } from "antd";
const useModal = ({
modalProps, content
}) => {
const [visible, setVisible] = useState(false);
const CustomModal = () => {
return (
<Modal
visible={visible}
maskClosable={false}
onCancel={() => {
setVisible(false);
}}
{...modalProps}
>
{content}
</Modal>
);
};
return {
setVisible, CustomModal
};
};
export default useModal;
使用:
import React, { useEffect, useCallback } from "react";
import useModalHook from "@/tools/useModalHook";
const Com = () => {
const onOk = useCallback(() => {
}, []);
const { setVisible, CustomModal } = useModalHook({
content: <div>弹窗内容</div>,
modalProps: {
title: "弹窗",
width: 680,
onOk
}
});
return <>
<button onClick={() => { setVisible(true) }}>显示弹窗</button>
<CustomModal />
</>
};
export default Com;
当然网上也有很多方便的自定义hook可以用,推荐阿里的ahooks