!! 文章系翻译,原文见
阅读原文
你肯定看过(或写过)这样的渲染模式:
AJAX
请求数据时渲染一个loading
占位图标让我们一个使用Fetch API
的简单例子:
import React, { useState, useEffect } from 'react';
const SomeComponent = (props) => {
const [someData, setSomeData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch('/some-data')
.then(response => response.json())
.then(data => setSomeData(data))
.catch(error => setError(error))
.finally(() => setLoading(false));
}, []);
return (
<React.Fragment>
{loading && <div>{'Loading...'}</div>}
{!loading && error && <div>{`Error: ${error}`}</div>}
{!loading && !error && someData && <div>{/* INSERT SOME AMAZING UI */}</div>}
</React.Fragment>
);
};
当我们的应用逐渐庞大,假设有n
个组件要使用同样的数据。
为了减少重复请求,我决定使用LocalStorage
缓存服务端数据。
这是否意味着同样的渲染逻辑要重复写n
次呢?
怎么可能,让我们将数据请求
部分抽离为一个自定义hook
——useSomeData
。
import React, { useState, useEffect } from 'react';
const useSomeData = () => {
const cachedData = JSON.parse(localStorage.getItem('someData'));
const [someData, setSomeData] = useState(cachedData);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!someData) {
setLoading(true);
fetch('/some-data')
.then(response => response.json())
.then(data => {
localStorage.setItem('someData', JSON.stringify(data));
return setSomeData(data);
})
.catch(error => setError(error))
.finally(() => setLoading(false));
}
}, []);
return { someData, loading, error };
};
使用useSomeData
看起来像这样:
const SomeComponent = (props) => {
const { someData, loading, error } = useSomeData();
return (
<React.Fragment>
{loading && <div>{'Loading...'}</div>}
{!loading && error && <div>{`Error: ${error}`}</div>}
{!loading && !error && someData && <div>{/* INSERT SOME AMAZING UI */}</div>}
</React.Fragment>
);
};
const AnotherComponent = (props) => {
const { someData, loading, error } = useSomeData();
return (
<React.Fragment>
{loading && <div>{'Loading...'}</div>}
{!loading && error && <div>{`Error: ${error}`}</div>}
{!loading && !error && someData && <div>{/* INSERT ANOTHER AMAZING UI */}</div>}
</React.Fragment>
);
};
复用代码挺棒的,但就仅此而已吧?
我们的应用越来越复杂,我决定上Redux
。
此时只需要简单的修改下useSomeData
,完全不需要改动业务组件:
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { selectSomeData } from 'path/to/data/selectors';
import { fetchSomeData } from 'path/to/data/action';
const useSomeData = () => {
const dispatch = useDispatch();
const someData = useSelector(selectSomeData);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!someData) {
setLoading(true);
dispatch(fetchSomeData())
.catch(error => setError(error))
.finally(() => setLoading(false));
}
}, []);
return { someData, loading, error };
};
某天我决定赶时髦上GraphQL
。同样,只需要简单修改useSomeData
而无需改动业务组件:
import { gql, useQuery } from '@apollo/client';
const FETCH_SOME_DATA = gql`
fetchSomeData {
data {
# some fields
}
}
`;
const useSomeData = () => {
const { data, loading, error } = useQuery(FETCH_SOME_DATA);
return { someData: data, loading, error };
};
每当我自愿(或被迫)修改数据请求/状态管理部分时,只需要修改对应的hook
就行。
就像经典的依赖倒置
原则(SOLID
中的D
)。尽管并非面向对象,但我们定义了一个抽象接口,并基于其实现了该接口的类。
useSomeData
实际上为使用他的业务组件提供了一个接口。
开发者不需要关心useSomeData
的实现原理,只需要关注接收到的数据、加载状态、错误信息即可。
理论上来说,只要定义合适的接口,就能将UI
从数据层解耦出来,并随时迁移到任何数据层上。
点击这里留言