React Hooks 的常见的错误写法
2020-07-13 本文已影响0人
vavid
一、使用 useState 导致了不必要的重新渲染
错误示例 ❌
function ClickButton(props) {
const [count, setCount] = useState(0);
const onClickCount = () => {
setCount((c) => c + 1);
};
const onClickRequest = () => {
apiCall(count);
};
return (
<div>
<button onClick={onClickCount}>Counter</button>
<button onClick={onClickRequest}>Submit</button>
</div>
);
}
暴露问题 ⚡️
react 中任何 state 更新都会触发组件以及它的子组件重新渲染。上面的示例我们没有在 render 部分用到那些 state,当我们每次设置计数器的时候将会触发不需要的渲染,可能会影响性能或者产生其它副作用。
推荐写法 ✅
function ClickButton(props) {
const count = useRef(0);
const onClickCount = () => {
count.current++;
};
const onClickRequest = () => {
apiCall(count.current);
};
return (
<div>
<button onClick={onClickCount}>Counter</button>
<button onClick={onClickRequest}>Submit</button>
</div>
);
}
二、用 router.push方法替代链接
错误示例 ❌
function ClickButton(props) {
const history = useHistory();
const onClick = () => {
history.push('/next-page');
};
return <button onClick={onClick}>Go to next page</button>;
}
暴露问题 ⚡️
虽然以上代码大部分场景下没问题,但是当涉及到可访问性时,却有个大问题,因为按钮不会被标记为链接到另一个页面,这使得几乎不可能被屏幕阅读器识别。 另外,你能在一个新的选项卡或窗口中打开它吗? 很可能不能。
推荐写法 ✅
function ClickButton(props) {
return (
<Link to="/next-page">
<span>Go to next page</span>
</Link>
);
}
三、通过 useEffect 处理 actions
错误示例 ❌
function DataList({ onSuccess }) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState(null);
const fetchData = () => {
setLoading(true);
callApi()
.then((res) => setData(res))
.catch((err) => setError(err))
.finally(() => setLoading(false));
};
useEffect(() => {
fetchData();
}, []);
useEffect(() => {
if (!loading && !error && data) {
onSuccess();
}
}, [loading, error, data, onSuccess]);
return <div>Data: {data}</div>;
}
暴露问题 ⚡️
这里有两个 useEffect hooks 函数,第一个是请求初始化渲染api数据,第二个是在函数加载完毕,没有出错,同时有数据返回时,调用 onSuccess 函数,对吧?
当然对于第一个,永远不可能有问题。但是我们不能保证第一个百分百成功,然后接下来去执行第二个。
推荐写法 ✅
function DataList({ onSuccess }) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState(null);
const fetchData = () => {
setLoading(true);
callApi()
.then((fetchedData) => {
setData(fetchedData);
onSuccess();
})
.catch((err) => setError(err))
.finally(() => setLoading(false));
};
useEffect(() => {
fetchData();
}, []);
return <div>{data}</div>;
}
四、单一职责的组件
错误示例 ❌
function Header(props) {
return (
<header>
<HeaderInner menuItems={menuItems} />
</header>
);
}
function HeaderInner({ menuItems }) {
return isMobile() ? <BurgerButton menuItems={menuItems} /> : <Tabs tabData={menuItems} />;
}
暴露问题 ⚡️
这种写法,使得组件 HeaderInner 承载了两个不同的功能,我们也通过 Mr. Jekyll 知道了一个组件做多件事情并不理想。此外,这样做在其他地方测试或重用组件也更加困难。
推荐写法 ✅
function Header(props) {
return (
<header>{isMobile() ? <BurgerButton menuItems={menuItems} /> : <Tabs tabData={menuItems} />}</header>
);
}
五、单一职责的 useEffects
错误示例 ❌
function Example(props) {
const location = useLocation();
const fetchData = () => {
/* Calling the api */
};
const updateBreadcrumbs = () => {
/* Updating the breadcrumbs*/
};
useEffect(() => {
fetchData();
updateBreadcrumbs();
}, [location.pathname]);
return (
<div>
<BreadCrumbs />
</div>
);
}
暴露问题 ⚡️
这里的 useEffect hook 里有两个功能,一个是“数据获取”,另一个是“展示面包屑”,当数据获取,请求面包屑函数或者 location 发生改变时,useEffect hooks 都会执行。现在的主要问题是,当 location 发生变化时,数据获取函数会重新执行,这可能是我们想不到的一个副作用。
推荐写法 ✅
function Example(props) {
const location = useLocation();
const updateBreadcrumbs = () => {
/* Updating the breadcrumbs*/
};
useEffect(() => {
updateBreadcrumbs();
}, [location.pathname]);
const fetchData = () => {
/* Calling the api */
};
useEffect(() => {
fetchData();
}, []);
return (
<div>
<BreadCrumbs />
</div>
);
}