34

Redux+Hook 重写 Todo List,用代码实例挽回摒弃 Redux 的用户

 5 years ago
source link: https://www.tuicool.com/articles/UZnMf27
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作者开发了一个名为“reactive-react-redux”的库,尽管它基于 Redux,但和传统方法又有一些区别。作者基于这个库给出了 Redux 中 Todo List 的示例代码。

如果你已经在用 React Redux 并爱上它,可能会不理解为什么人们尝试使用 React 中的 context 和 hook 来替换 Redux,即所谓“去 Redux 化”。

有些人认为 Redux DevTools 的扩展工具和中间件蛮不错的,对于他们来说,Redux 和 context + hook 实际上是两种选项。Context + hook 可以在组件之间实现状态共享,但是随着 APP 变得越来越大,有可能还是需要引入 Redux 或其他类似的解决方案,否则,最终运行中会出现太多上下文而无法进行顺畅处理。但是,我得承认这只是假设,将来或许能够找到更好的解决方案。

我最近一直在开发一个名为“reactive-react-redux”的库,尽管它基于 Redux,但和传统方法又有一些区别。Github 地址: https://github.com/dai-shi/reactive-react-redux

它的 API 非常简单直观,而且 Proxy 让它的性能得到了优化。我希望这个库能挽回一些用 context + hook 去替代 Redux 的开发人员,为此我还基于这个库写了代码示例。下面这个 示例 实现的是 Redux 中著名的 Todo List。

这个示例是用 TypeScript 语言写的。如果你不熟悉 TypeScript,请尝试忽略 State、Action 和 *Type 这些关键字。

类型定义和状态还原器(reducer)

State 和 Action 的类型定义定义如下:

./src/types/index.ts

复制代码

exporttypeVisibilityFilterType =
|'SHOW_ALL'
|'SHOW_COMPLETED'
|'SHOW_ACTIVE';

exporttypeTodoType = {
id:number;
text:string;
completed:boolean;
};

exporttypeState = {
todos: TodoType[];
visibilityFilter: VisibilityFilterType;
};

exporttypeAction =
| {type:'ADD_TODO'; id:number; text:string}
| {type:'SET_VISIBILITY_FILTER'; filter: VisibilityFilterType }
| {type:'TOGGLE_TODO'; id:number};

状态还原器(reducer)的代码几乎与原始示例一样,如下所示。

./src/reducers/index.ts

复制代码

import{ combineReducers }from'redux';

importtodosfrom'./todos';
importvisibilityFilterfrom'./visibilityFilter';

exportdefaultcombineReducers({
todos,
visibilityFilter,
});

./src/reducers/todos.ts

复制代码

import { TodoType, Action }from'../types';

const todos = (state: TodoType[] = [], action: Action): TodoType[] => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false,
},
];
case 'TOGGLE_TODO':
returnstate.map((todo: TodoType) => (
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
));
default:
returnstate;
}
};

exportdefaulttodos;

./src/reducers/visibilityFilter.ts

复制代码

import{ Action, VisibilityFilterType }from'../types';

constvisibilityFilter = (
state: VisibilityFilterType ='SHOW_ALL',
action: Action,
):VisibilityFilterType=>{
switch(action.type) {
case'SET_VISIBILITY_FILTER':
returnaction.filter;
default:
returnstate;
}
};

exportdefaultvisibilityFilter;

动作生成器(Action creators)

有几种方法都可以用来实现动作分派。而我的选择是为每个动作创建 hook。注意,这方面我们仍在探索更好的实现方式。

./src/actions/index.ts

复制代码

import{ useCallback }from'react';
import{ useReduxDispatch }from'reactive-react-redux';

import{ Action, VisibilityFilterType }from'../types';

letnextTodoId =0;

exportconstuseAddTodo =()=>{
constdispatch = useReduxDispatch<Action>();
returnuseCallback((text:string) =>{
dispatch({
type:'ADD_TODO',
id: nextTodoId++,
text,
});
}, [dispatch]);
};

exportconstuseSetVisibilityFilter =()=>{
constdispatch = useReduxDispatch<Action>();
returnuseCallback((filter: VisibilityFilterType) =>{
dispatch({
type:'SET_VISIBILITY_FILTER',
filter,
});
}, [dispatch]);
};

exportconstuseToggleTodo =()=>{
constdispatch = useReduxDispatch<Action>();
returnuseCallback((id:number) =>{
dispatch({
type:'TOGGLE_TODO',
id,
});
}, [dispatch]);
};

以上实现其实并非真正意义上的动作生成器,而是返回动作分派器的 hook。

组件

我们并不在这里区分演示组件(presentational components)和容器组件(container components)。当然如何构造组件仍然是个值得探讨的话题,但是在本例中,组件都被视为扁平的。

./src/components/App.tsx:App 也和原始示例保持一致。

复制代码

import*asReactfrom'react';

importFooterfrom'./Footer';
importAddTodofrom'./AddTodo';
importVisibleTodoListfrom'./VisibleTodoList';

constApp: React.FC =()=>(
<div>
<AddTodo/>
<VisibleTodoList/>
<Footer/>
</div>
);

exportdefaultApp;

./src/components/Todo.tsx:这里做了一些小的修改,但没有特别大的改动。

复制代码

import*asReactfrom'react';

type Props = {
onClick:(e: React.MouseEvent) =>void;
completed: boolean;
text: string;
};

constTodo: React.FC<Props> =({ onClick, completed, text }) =>(
<li
onClick={onClick}
role="presentation"
style={{
textDecoration:completed? 'line-through':'none',
cursor:'pointer',
}}
>
{text}
</li>
);

exportdefaultTodo;

./src/components/VisibleTodoList.tsx:这里并未出现 mapStateToProps 或 selector 函数,只是在 render 中调用 getVisibleTodos。

复制代码

import*asReactfrom'react';
import{ useReduxState }from'reactive-react-redux';

import{ TodoType, State, VisibilityFilterType }from'../types';
import{ useToggleTodo }from'../actions';
importTodofrom'./Todo';

constgetVisibleTodos =(todos: TodoType[], filter: VisibilityFilterType) =>{
switch(filter) {
case'SHOW_ALL':
returntodos;
case'SHOW_COMPLETED':
returntodos.filter(t=>t.completed);
case'SHOW_ACTIVE':
returntodos.filter(t=>!t.completed);
default:
thrownewError(`Unknown filter:${filter}`);
}
};

constVisibleTodoList: React.FC =()=>{
conststate = useReduxState<State>();
constvisibleTodos = getVisibleTodos(state.todos, state.visibilityFilter);
consttoggleTodo = useToggleTodo();
return(
<ul>
{visibleTodos.map(todo => (
<Todokey={todo.id}{...todo}onClick={()=>toggleTodo(todo.id)} />
))}
</ul>
);
};

export default VisibleTodoList;

./src/components/FilterLink.tsx:同样,当 useReduxState 函数返回整个 Redux 状态对象时,程序只是使用其属性对 active 进行评估。

复制代码

import*asReactfrom'react';
import{ useReduxState }from'reactive-react-redux';

import{ useSetVisibilityFilter }from'../actions';
import{ State, VisibilityFilterType }from'../types';

type Props = {
filter: VisibilityFilterType;
};

constFilterLink: React.FC<Props> =({ filter, children }) =>{
conststate = useReduxState<State>();
constactive = filter === state.visibilityFilter;
constsetVisibilityFilter = useSetVisibilityFilter();
return(
<button
type="button"
onClick={()=> setVisibilityFilter(filter)}
disabled={active}
style={{
marginLeft: '4px',
}}
>
{children}
</button>
);
};

exportdefaultFilterLink;

./src/components/Footer.tsx:由于有类型检查的保证,可以将字符串传递给 FilterLink 组件的 filter 属性。

复制代码

import*asReactfrom'react';

importFilterLinkfrom'./FilterLink';

constFooter: React.FC =()=>(
<div>
<span>Show:</span>
<FilterLinkfilter="SHOW_ALL">All</FilterLink>
<FilterLinkfilter="SHOW_ACTIVE">Active</FilterLink>
<FilterLinkfilter="SHOW_COMPLETED">Completed</FilterLink>
</div>
);

exportdefaultFooter;

./src/components/AddTodo.tsx:这里对原始示例进行了一些修改,以便使用带有 useState 的受控表单。

复制代码

import*asReactfrom'react';
import{ useState }from'react';

import{ useAddTodo }from'../actions';

constAddTodo =()=>{
const[text, setText] = useState('');
constaddTodo = useAddTodo();
return(
<div>
<form
onSubmit={(e)=> {
e.preventDefault();
if (!text.trim()) {
return;
}
addTodo(text);
setText('');
}}
>
<inputvalue={text}onChange={e=>setText(e.target.value)} />
<buttontype="submit">Add Todo</button>
</form>
</div>
);
};

export default AddTodo;

在线演示

请打开你的浏览器访问 codesandbox ,运行该示例。你也可以在 GitHub 上找到所有源代码。

其他信息

这篇文章中,并没有解释关于 reactive- response -redux 的内部细节。请访问 GitHub 查看更多信息。

英文原文: https://blog.axlight.com/posts/redux-meets-hooks-for-non-redux-users-a-small-concrete-example-with-reactive-react-redux/

iYV7VfA.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK