写点什么

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

  • 2019-06-24
  • 本文字数:3985 字

    阅读完需:约 13 分钟

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

作者开发了一个名为“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


export type VisibilityFilterType =  | 'SHOW_ALL'  | 'SHOW_COMPLETED'  | 'SHOW_ACTIVE';
export type TodoType = { id: number; text: string; completed: boolean;};
export type State = { todos: TodoType[]; visibilityFilter: VisibilityFilterType;};
export type Action = | { 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';
import todos from './todos';import visibilityFilter from './visibilityFilter';
export default combineReducers({ 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': return state.map((todo: TodoType) => ( todo.id === action.id ? { ...todo, completed: !todo.completed } : todo )); default: return state; }};
export default todos;
复制代码


./src/reducers/visibilityFilter.ts


import { Action, VisibilityFilterType } from '../types';
const visibilityFilter = ( state: VisibilityFilterType = 'SHOW_ALL', action: Action,): VisibilityFilterType => { switch (action.type) { case 'SET_VISIBILITY_FILTER': return action.filter; default: return state; }};
export default visibilityFilter;
复制代码

动作生成器(Action creators)

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


./src/actions/index.ts


import { useCallback } from 'react';import { useReduxDispatch } from 'reactive-react-redux';
import { Action, VisibilityFilterType } from '../types';
let nextTodoId = 0;
export const useAddTodo = () => { const dispatch = useReduxDispatch<Action>(); return useCallback((text: string) => { dispatch({ type: 'ADD_TODO', id: nextTodoId++, text, }); }, [dispatch]);};
export const useSetVisibilityFilter = () => { const dispatch = useReduxDispatch<Action>(); return useCallback((filter: VisibilityFilterType) => { dispatch({ type: 'SET_VISIBILITY_FILTER', filter, }); }, [dispatch]);};
export const useToggleTodo = () => { const dispatch = useReduxDispatch<Action>(); return useCallback((id: number) => { dispatch({ type: 'TOGGLE_TODO', id, }); }, [dispatch]);};
复制代码


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

组件

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


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


import * as React from 'react';
import Footer from './Footer';import AddTodo from './AddTodo';import VisibleTodoList from './VisibleTodoList';
const App: React.FC = () => ( <div> <AddTodo /> <VisibleTodoList /> <Footer /> </div>);
export default App;
复制代码


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


import * as React from 'react';
type Props = { onClick: (e: React.MouseEvent) => void; completed: boolean; text: string;};
const Todo: React.FC<Props> = ({ onClick, completed, text }) => ( <li onClick={onClick} role="presentation" style={{ textDecoration: completed ? 'line-through' : 'none', cursor: 'pointer', }} > {text} </li>);
export default Todo;
复制代码


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


import * as React from 'react';import { useReduxState } from 'reactive-react-redux';
import { TodoType, State, VisibilityFilterType } from '../types';import { useToggleTodo } from '../actions';import Todo from './Todo';
const getVisibleTodos = (todos: TodoType[], filter: VisibilityFilterType) => { switch (filter) { case 'SHOW_ALL': return todos; case 'SHOW_COMPLETED': return todos.filter(t => t.completed); case 'SHOW_ACTIVE': return todos.filter(t => !t.completed); default: throw new Error(`Unknown filter: ${filter}`); }};
const VisibleTodoList: React.FC = () => { const state = useReduxState<State>(); const visibleTodos = getVisibleTodos(state.todos, state.visibilityFilter); const toggleTodo = useToggleTodo(); return ( <ul> {visibleTodos.map(todo => ( <Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} /> ))} </ul> );};
export default VisibleTodoList;
复制代码


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


import * as React from 'react';import { useReduxState } from 'reactive-react-redux';
import { useSetVisibilityFilter } from '../actions';import { State, VisibilityFilterType } from '../types';
type Props = { filter: VisibilityFilterType;};
const FilterLink: React.FC<Props> = ({ filter, children }) => { const state = useReduxState<State>(); const active = filter === state.visibilityFilter; const setVisibilityFilter = useSetVisibilityFilter(); return ( <button type="button" onClick={() => setVisibilityFilter(filter)} disabled={active} style={{ marginLeft: '4px', }} > {children} </button> );};
export default FilterLink;
复制代码


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


import * as React from 'react';
import FilterLink from './FilterLink';
const Footer: React.FC = () => ( <div> <span>Show: </span> <FilterLink filter="SHOW_ALL">All</FilterLink> <FilterLink filter="SHOW_ACTIVE">Active</FilterLink> <FilterLink filter="SHOW_COMPLETED">Completed</FilterLink> </div>);
export default Footer;
复制代码


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


import * as React from 'react';import { useState } from 'react';
import { useAddTodo } from '../actions';
const AddTodo = () => { const [text, setText] = useState(''); const addTodo = useAddTodo(); return ( <div> <form onSubmit={(e) => { e.preventDefault(); if (!text.trim()) { return; } addTodo(text); setText(''); }} > <input value={text} onChange={e => setText(e.target.value)} /> <button type="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/



2019-06-24 19:335641
用户头像

发布了 63 篇内容, 共 43.0 次阅读, 收获喜欢 119 次。

关注

评论

发布
暂无评论
发现更多内容

【面经】面试官:讲讲类的加载、链接和初始化?

冰河

架构 JVM 类加载 优化 性能调试

Scrapy源码剖析(一)架构概览

Kaito

Python 爬虫 Scrapy 源码剖析

Scrapy 源码剖析(四)Scrapy如何完成抓取任务?

Kaito

Python 爬虫 Scrapy 源码剖析

科大讯飞翻译系统变身“随身翻译官” 助力粤港澳大湾区一体化建设

Talk A.I.

央行数字货币亮相 吹皱一池春水

CECBC

数字货币 银行

目标检测学习-比赛路线

Dreamer

SpringCloud 和 SpringBoot 版本选型

hepingfly

微服务 springboot SpringCloud 选型

Java9新特性-上篇

hepingfly

Java Java新特性

如何构建一个通用的垂直爬虫平台?

Kaito

Python 爬虫 代理

架构师训练营第二周

M.

架构师训练营第二周作业

lakers

极客大学架构师训练营

有状态软件如何在k8s上快速扩容甚至自动扩容

东风微鸣

Kubernetes DevOps openshift

架构师训练营第 1 期 -- 第六周学习总结

发酵的死神

极客大学架构师训练营

美国半导体十年计划中的NO.1,模拟硬件究竟有什么价值?

脑极体

架构师训练营第 1 期 第 6 周作业

李循律

极客大学架构师训练营

元模型驱动(三):构建我们自己的元模型-KAYA

KaYa

DDD Kaya MDA MDD 元建模

嵌入式面试之《Linux系统编程100问》

哒宰的自我修养

Linux 线程 网络编程 进程

架构师训练营第 1 期 -- 第六周作业

发酵的死神

极客大学架构师训练营

当AI入职FBI,克格勃直呼内行

脑极体

CICD实战——服务自动测试

TARS基金会

微服务 单元测试 CI/CD

TarsCpp 组件 之 智能指针详解

TARS基金会

c++ 微服务 智能指针 TARS

甲方日常42

句子

工作 随笔杂谈 日常

如何搭建一个爬虫代理服务?

Kaito

爬虫 代理

2020年9月公有云性能评测:UCloud、腾讯云屈居二三名,冠军竟然是它?

博睿数据

云计算 公有云 评测 排行榜

2020年区块链行业十大趋势

CECBC

区块链 技术人才

「架构师训练营第 1 期」第六周作业

张国荣

积极参与数字货币等国际规则制定,塑造新的竞争优势

CECBC

疫情 经济

TarsCpp 组件之 MySQL 操作

TARS基金会

c++ MySQL 数据库 微服务 TARS

架构师训练营第二周总结

lakers

极客大学架构师训练营

Scrapy 源码剖析(二)Scrapy是如何运行起来的?

Kaito

Python 爬虫 Scrapy 源码剖析

Scrapy 源码剖析(三)Scrapy有哪些核心组件?

Kaito

Python 爬虫 Scrapy 源码剖析

Redux+Hook重写Todo List,用代码实例挽回摒弃Redux的用户_大前端_Daishi Kato_InfoQ精选文章