真实世界的应用涉及到通过一些API加载数据,并根据API的状态显示UI。例如,当数据正在加载时,你可能会显示一个加载器动画,但在错误时,你可能会显示一个错误的用户界面。这个看起来相当简单的任务最终会变得非常复杂,并且由于所有用于UI同步的意大利语代码而更难维护。因此,我在这里提出了可加载模式,以简化数据加载,并使UI与之同步。
在这个例子中,我们将加载一个todos的列表。这里我们使用react-redux作为状态管理解决方案。下面我们将看到如何用 react-redux 创建一个存储和还原器。然而,如果你熟悉 react-redux-context store,你可以直接跳到 "loadables"。
创建 react-redux 上下文存储
让我们开始创建一个 react-redux-context-store 来存储我们的 todos。下面的例子取自react-redux:
// [filename: todo.store.jsx]
import React from 'react'
import {
Provider,
createStoreHook,
createDispatchHook,
createSelectorHook,
from "react-redux";
import { createStore } from "redux";
// reducer for the state
import { reducer } from "./store.reducer"
// react context store
const TodoContext = React.createContext(null)
// create redux state selector and dispatch from context
export const useTodoStore = createStoreHook(TodoContext)
export const useTodoDispatch = createDispatchHook(TodoContext)
export const useTodoSelector = createSelectorHook(TodoContext)
// create redux store from the reducer
const todoStore = createStore(reducer)
// create store provider wrap subtree
export function TodoStoreProvider({ children }) {
return (
<Provider context={TodoContext} store={todoStore}>
{children}
</Provider>
)
}
在创建一个存储提供者之后,我们将创建store.reducer.js ,在这里我们定义存储的还原器和动作:
// [filename: todo.reducer.js]
export const loadNext = () => ({ type: 'load_next' });
export const addTodos = ({ todos, total }) => ({ type: 'add_todos', payload: { todos, total } });
export const setLoading = (loading) => ({ type: 'set_loading', payload: { loading } });
const InitState = {
status: 'idle', // idle | pending | resolve | reject
todos: [],
total: 0,
skip: 0,
limit: 10
};
export const reducer = (state = InitState, action) => {
switch (action.type) {
case 'load_next': {
if (state.todos.length < state.total && state.status !== 'pending') {
return {
...state,
status: 'pending'
};
}
return state;
}
case 'add_todos': {
return {
...state,
status: 'resolve',
todos: [...state.todos, ...action.payload.todos],
total: state.total + action.payload.todos.length
};
}
case 'set_loading': {
return {
...state,
status: action.payload.loading
};
}
default: {
return state;
}
}
};
可加载性
Loadable 是 react 组件,它将所有的数据加载逻辑包裹在其中并更新存储:
// [filename: App.js]
const App = () => (
<div>
<TodoStoreProvider>
{/* Loadable holds all data loading logic*/}
<TodoLoadable>
{/* Render todos */}
</TodoLoadable>
</TodoStoreProvider>
</div>
);
现在让我们来创建一个可加载的组件:
// [filename: Todo.loadable.js]
function TodoLoadable(props) {
// react-redux state slice selector
const skip = useTodoSelector((state) => state.skip);
const limit = useTodoSelector((state) => state.limit);
const todoDispatch = useTodoDispatch();
// load data
useEffect(() => {
todoDispatch(setLoading('pending'));
api({ skip, limit })
.then((res) => todoDispatch({ todos: res.todos, total: res.total }))
.catch((e) => todoDispatch(setLoading('reject')));
}, [skip, limit]);
// render child
return <>{props.children}</>
}
这里需要注意的是,加载逻辑完全放在可加载的内部,并且孩子们可以利用商店来相应地同步UI状态。IsVisible 是一个实用组件,可以用来有条件地渲染东西:
// [filename: IsVisible.utility.jsx]
function IsVisible({ visible, unmountOnExit, ...props }) {
if (unmountOnExit && !visible) {
return null;
}
return <div {...props} style={{ ...props.style, display: visible ? 'flex' : 'none' }} />
}
我们可以使用IsVisible 实用组件来创建状态同步的UI:
// [filename: Todo.jsx]
const Error = () => <div><h1>Error</h1></div>;
const Loader = () => <CircularProgress size="small" />
const Todos = () => {
const todos = useTodoSelector((state) => state.todos);
return <div>{todos.map((todo) => <h1>{todo}</h1>)}</div>
}
function IsErrorVisible(props) {
const isError = useTodoSelector((state) => state.status === 'reject');
return <IsVisible {...props} visible={isError} />
}
....more IsVisible for all API status 'reject' | 'resolve' | 'pending' | 'idle'
现在在这个IsVisible 的帮助下,我们可以根据API的状态来渲染UI:
// [filename: App.js]
const App = () => (
<div>
<TodoStoreProvider>
{/* Loadable holds all data loading logic*/}
<TodoLoadable>
<IsErrorVisible><ErrorUI /></IsErrorVisible>
<IsTodoVisible><Todos /></IsTodoVisible>
<IsLoaderVisible><Loader /></IsLoaderVisible>
</TodoLoadable>
</TodoStoreProvider>
</div>
);
这就是loadable 和IsVisible 工具如何使在react中加载数据变得超级容易,并使代码的编写和理解变得简单。这里有一个演示Codesandbox的链接。