Эта статья также находится на моей платформе блога:betamee.github.ioВидно, добро пожаловать на общение~
содержание
- 1. Введение
- 2. Решение Redux для управления состоянием
- 3. Решение MobX для управления состоянием
- 4. Как выглядит этот пример
- 5. Classic React App with Redux
- 6. Classic React App with Mobx
- 7. React Hooks App with Redux
- 8. React Hooks App with Mobx
- 9. Резюме
- Ссылаться на
1. Введение
Управление состоянием всегда было важной темой во все более сложных веб-приложениях. Также существует множество решений, производных от стека технологий React, таких как Redux и Mobx, а с выпуском React Hooks был введен новый режим написания кода, и произошло много новых изменений в использовании библиотек управления состоянием.
Чтобы указать на управление состоянием React, я написал несколько примеров и сравнил различия в использовании.
Разработайте то же приложение Todo, но используйте его следующими четырьмя способами:
- React + Redux с использованием шаблона класса
- React + Mobx с использованием шаблона класса
- React + Redux с использованием шаблона Hooks
- React + Mobx с использованием шаблона Hooks
Весь код опубликован в этом репозитории на Github.GitHub - BetaMee/react-usage-examples: React Usage Examplesвыше, для справки.
2. Решение Redux для управления состоянием
Redux — очень популярное техническое решение, и сразу же после запуска оно привлекло большое внимание. Редукс отFluxРазработанный, но вдохновленный Вязом, избегайте сложности Flux. Он предоставляет простой, но эффективный API, обеспечивающий предсказуемое управление состоянием. Но кривая обучения очень крутая, потому что она приносит новые концепции и условности, и только благодаря пониманию мы можем лучше их применять.
Чтобы понять Redux, его можно разделить на следующие пункты:
- Все состояние хранится в одном хранилище в виде дерева объектов
- UI — это карта состояния дерева состояний в определенный момент
- Единственный способ изменить дерево состояний — запустить действие, объект, который описывает, что произошло.
- Что касается конкретных изменений в дереве состояний, то они реализуются чистыми функциями, называемыми редюсерами.
- Обновленное дерево состояний сопоставляется с новым пользовательским интерфейсом.
Из вышеизложенного можно сделать вывод, чтоТри принципа Redux:
- единственный источник правды
- состояние только для чтения
- Используйте чистые функции для выполнения модификаций
После понимания вышеуказанных концепций вы можете начать работу с Redux. Но только вышеперечисленные концепции не могут решить более сложные сценарии веб-приложений, такие как асинхронная загрузка данных для обновления интерфейса.
Для этого необходимо задействовать Redux впромежуточное ПОконцепция.
Используя промежуточное программное обеспечение, мы можем выполнять операции с данными при изменении состояния.отслеживать, перехватывать, изменять, а также может расширять асинхронные операции. Это дает нам большую свободу использовать Redux в полной мере.
Промежуточное ПО Redux — это, по сути, функция, и это правильно.store.dispatch
Метод был переработан, чтобы добавить определенные функции между этапами выдачи действия и выполнения редюсера.
Сообщество Redux разработало множество зрелых промежуточных программ, таких как redux-thunk и redux-soga, которые нужно использовать только по запросу.
Для получения подробной информации см.:redux#ecosystem#middlewareи китайская версияcnredux#ecosystem
3. Решение MobX для управления состоянием
Подобно Redux, еще одно решение для управления состоянием, появившееся во внешнем интерфейсе, — это MobX.
Официальный сайт MobX описывает это так:
MobX — это разоренная войной библиотека, которая делает управление состоянием простым и расширяемым за счет прозрачного применения функционального реактивного программирования (TFRP). Философия MobX проста:Все, что происходит из состояния приложения, должно быть получено автоматически.К ним относятся пользовательский интерфейс, сериализация данных, связь с сервером и многое другое.
На самом деле, по сравнению со строгими правилами и соглашениями Redux, MobX, очевидно, проще и гибче.Основной принцип MobX заключается в следующем.Инициировать изменение состояния посредством действия, а затем активировать производный объект состояния (вычисляемое значение и реакции).. Разработчикам нужно только определить данные, которые необходимо наблюдать, и производные данные (вычисляемое значение) или операции (реакции), а остальные обновления, естественно, будут выполняться MobX.
Сравнение MobX и Redux примерно показывает эти различия:
- Redux функционален, а MobX объектно-ориентирован.
- Состояние Redux каждый раз возвращает новые данные, идеальное состояние неизменно, а MobX сохраняет ссылку от начала до конца.
- Redux может поддерживать откат данных, MobX не может поддерживать откат из-за только одной ссылки
- Redux соглашается на единый источник данных, а MobX может разделить дерево состояний на несколько хранилищ.
- Redux нуждается в промежуточном программном обеспечении для обработки асинхронности, MobX может напрямую использовать async/await для ее обработки.
Короче говоря, по сравнению с этими двумя, с MobX действительно легче начать работу, чем с Redux, и не нужно писать много шаблонного кода, но «сложность» Redux не обязательно бесполезна, и правила строгого соглашения могут может использоваться для больших проектов, сложных состояний данных. Управление обеспечивает надежную поддержку, и MobX действительно может предоставить более эффективный вариант, чем проекты Redux в некоторых сценариях.
Но усложнить Redux непросто, а MobX упрощает, тут еще зависит от подходящей сцены. Мы должны уделять больше внимания тому, какие проблемы они решают, проблемы, которые они решают, или методы реализации, каковы их преимущества и недостатки, какой из них больше подходит для текущего проекта и будущего развития проекта.
4. Как выглядит этот пример
Это Todo, которое мы сделали, включает в себя только простые добавления и удаления:
5. Classic React App with Redux
Далее я специально представлю использование четырех примеров и извлеку из них уроки.Режим организации проекта, режим написания кода, взгляните на разработку приложений библиотеки управления состоянием React.
Первое — это классическое приложение React с Redux, разработанное с использованием «традиционного» шаблона классов + Redux. Этот шаблон существует с тех пор, как Redux был впервые представлен.
Мы лежащим нашему приложением запуска с лесами Create-React-App, которые сэкономят нас много времени:
npx create-react-app classicreactwithredux --typescript
Здесь используется шаблон Typescript, и все приложение будет разработано в TS, так что это также возможность обучения изучению TS.
Вот файл кода для всего приложения:
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── src // 源码文件,所有的代码在这里
│ ├── App.css
│ ├── App.test.tsx
│ ├── App.tsx // App 入口
│ ├── components // UI 组件
│ │ ├── TodoAdd.tsx
│ │ ├── TodoCounter.tsx
│ │ ├── TodoView.tsx
│ │ ├── TodoViewList.tsx
│ │ ├── assets
│ │ │ ├── delete.svg
│ │ │ ├── select.svg
│ │ │ └── select2.svg
│ │ └── styles
│ │ ├── TodoAdd.css
│ │ ├── TodoCounter.css
│ │ ├── TodoView.css
│ │ └── TodoViewList.css
│ ├── containers // 容器组件
│ │ ├── TodoAddContainer.ts
│ │ ├── TodoCounterContainer.ts
│ │ └── TodoViewListContainer.ts
│ ├── index.css
│ ├── index.tsx // 整个应用的入口文件
│ ├── interfaces // 定义的 ts 接口文件
│ │ ├── component.ts
│ │ └── index.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ └── store // 这里放置 store 文件
│ ├── store.ts
│ └── todo // 按角色划分 state 树,一个文件夹一个角色
│ ├── actions.ts
│ ├── index.ts
│ ├── reducers.ts
│ └── types.ts
├── tsconfig.json
└── yarn.lock
index.tsx
В качестве входного файла он берет на себя задачу глобальной инициализации, и здесь могут быть размещены такие задачи, как инициализация хранилища и приложения рендеринга.
мы проходимreact-redux
который предоставилProvider
пакетApp
Компоненты, вводят данные хранилища в глобальное приложение, следующие компоненты могут определенным образом получать избыточные данные. Позже я расскажу, как использовать его в компоненте-контейнере.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import configureStore from './store/store'
// 初始化 redux store
const AppStore = configureStore()
ReactDOM.render(
<Provider store={AppStore}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
существуетconfigureStore
В функции выполняется задача совмещения состояния root и загрузки middleware, вся настройка redux в основном здесь:
import {
createStore,
combineReducers
} from 'redux';
import { todoReducer } from './todo/';
// 根 rootReducer
const rootReducer = combineReducers({
todoReducer: todoReducer
})
// 导出store 类型
export type AppState = ReturnType<typeof rootReducer>
const configureStore = () => {
// 组合中间件,这个例子没有用到
// const middlewares = [thunkMiddleware];
// const middleWareEnhancer = applyMiddleware(...middlewares);
// 创建 store
const store = createStore(rootReducer);
return store;
}
export default configureStore
export * from './todo'
Здесь нажимаем состояние ReduxРольРаздел, например, данные состояния todo здесь, мы настраиваем его в папке todoaction
,reducers
item, а затем единообразно экспортировать его:
│ └── todo // 按角色划分 state 树,一个文件夹一个角色
│ ├── actions.ts
│ ├── index.ts
│ ├── reducers.ts
│ └── types.ts
Преимущество этого в том, что зависимости понятны, а последующее расширение очень удобно.Например, чтобы добавить новые данные о состоянии, вам нужно только добавить новую папку и поставить это "Роль«Логика этого может быть ясно написана.
Конечно, Redux можно разделить не только по ролям, но и поФункцияразделены, как и всеactions
Объединены в папку действий,reducers
Поместите его в папку рендереров:
// 示例:按功能拆分文件夹
│ └── actions
│ ├── index.ts
│ └── renduers
│ ├── index.ts
│ └── types
│ ├── index.ts
Далее давайте посмотрим, как написать логику редуктора и действия в файле todo:
reducers.ts:
import {
TodoReducerType,
TodoActionType
} from './types'
import {
ADD_TODO,
REMOVE_TODO,
SELECT_TODO
} from './types'
// 初始化状态
const initalState: TodoReducerType = []
export const todoReducer = (state = initalState, action: TodoActionType): TodoReducerType => {
switch(action.type) {
case ADD_TODO:
return [...state, {
id: Math.random(),
name: action.name,
finished: false
}]
case REMOVE_TODO:
return state.filter(todo => todo.id !== action.id)
case SELECT_TODO:
return state.map(todo => {
if (todo.id === action.id) {
return {
...todo,
finished: !todo.finished
}
} else {
return todo
}
})
default:
return state;
}
}
actions.ts:
import {
ADD_TODO,
REMOVE_TODO,
SELECT_TODO
} from './types'
import {
TodoActionType
} from './types'
export const addTodo = (name: string): TodoActionType => ({
type: ADD_TODO,
name: name
})
export const removeTodo = (id: number): TodoActionType => ({
type: REMOVE_TODO,
id: id
})
export const selectTodo = (id: number): TodoActionType => ({
type: SELECT_TODO,
id: id
})
Эти два файла определяют следующие две ключевые точки каждого состояния персонажа, которые являются сутью использования Redux:
- действие чистая функция: описать, как изменить состояние (что есть)
- Чистая функция Reducer: описываем логику изменения состояния (как это сделать)
вactions.ts
Функции внутри — это те, которые нам нужно импортировать в компоненты пользовательского интерфейса, чтобы функционировать.
Итак, как мыactions
(операция) иstate
(состояние) конкретного импорта в компонент?
Это вопрос, на который должен ответить компонент контейнера в папке контейнера:
│ ├── containers // 容器组件
│ │ ├── TodoAddContainer.ts
│ │ ├── TodoCounterContainer.ts
│ │ └── TodoViewListContainer.ts
На самом деле, не вызывайте Reduxкомпонент контейнера,Компоненты пользовательского интерфейсаКонцепция сбивает с толку, на самом деле это промежуточный шаг для соединения компонентов Redux и React.
здесь сTodoViewListContainer.ts
Например:
import { connect } from 'react-redux'
import {
Dispatch
} from 'redux'
import TodoViewList from '../components/TodoViewList'
import {
AppState,
TodoActionType,
removeTodo,
selectTodo
} from '../store/store'
const mapStateToProps = (state: AppState) => ({
todos: state.todoReducer
})
const mapDispatchToProps = (dispatch: Dispatch<TodoActionType>) => ({
removeTodoById: (id: number) => dispatch(removeTodo(id)),
selectTodoById: (id: number) => dispatch(selectTodo(id))
})
export default connect(mapStateToProps, mapDispatchToProps)(TodoViewList)
в,TodoViewList
это конкретный компонент React,mapStateToProps
,mapDispatchToProps
Как следует из названия, действие и состояние используются как React.props
Импорт вниз.
затем вTodoViewList
В компоненте мы используем его так:
import React from 'react'
import TodoView from './TodoView'
import './styles/TodoViewList.css'
import {
IViewListProp
} from '../interfaces'
const TodoViewList: React.FC<IViewListProp> = ({
todos,
selectTodoById,
removeTodoById
}) => (
<div className="viewlist">
{todos.map((item, index) => (
<TodoView
todo={item}
selectTodoById={selectTodoById}
removeTodoById={removeTodoById}
key={index}
/>
))}
</div>
)
export default TodoViewList
Это комбинация React и Redux. На этом вся работа в основном завершена. Пока состояние меняется в будущем, компонент будет обновляться, иЕдинственный способ изменить состояние — через функцию входящего действия.. Таким образом, понятны зависимости всего приложения, а также понятны и регламентированы изменения в потоке данных, что очень удобно для устранения неполадок.
6. Classic React App with Mobx
Далее давайте взглянем на приложение Todo, использующее архитектуру шаблона класса + MobX.
Давайте посмотрим на общую файловую структуру здесь:
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── src // 源码
│ ├── App.css
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── components // UI 组件
│ │ ├── TodoAdd.tsx
│ │ ├── TodoCounter.tsx
│ │ ├── TodoView.tsx
│ │ ├── TodoViewList.tsx
│ │ ├── assets
│ │ │ ├── delete.svg
│ │ │ ├── select.svg
│ │ │ └── select2.svg
│ │ └── styles
│ │ ├── TodoAdd.css
│ │ ├── TodoCounter.css
│ │ ├── TodoView.css
│ │ └── TodoViewList.css
│ ├── index.css
│ ├── index.tsx // 入口
│ ├── interfaces // 定义的 ts 接口
│ │ ├── base.ts
│ │ └── index.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ └── store // 存放 mobx store
│ ├── TodoListStore.ts
│ └── index.ts
├── tsconfig.json
└── yarn.lock
Самая большая разница с Redux заключается в том, что Redux — это функционально-ориентированное мышление, а MobX —объектно-ориентированныймышление. Давайте посмотрим, как определяется магазин в MobX:
import {
observable,
computed,
action
} from 'mobx'
import {
ITodo
} from '../interfaces'
class Todo {
id = Math.random();
@observable name: string;
@observable finished = false;
constructor(name: string) {
this.name = name;
}
}
class TodoListStore {
@observable todos: Array<ITodo> = []
@computed get finishedTodoCount() {
return this.todos.filter(todo => todo.finished).length
}
@computed get totalCount() {
return this.todos.length
}
@action
public finishTodoById = (id: number) => {
this.todos.forEach(todo => {
if (todo.id === id) {
todo.finished = !todo.finished
}
})
}
@action
public addNewTodo = (name: string) => {
this.todos.push(new Todo(name))
}
@action
public removeTodoById = (id: number) => {
// 找到某一项的位置
const index = this.todos.findIndex(todo => todo.id === id)
this.todos.splice(index, 1)
}
}
export default TodoListStore
Мы определяем класс, который инкапсулируетсостояние + функция, отметьте статус@observable
Декоратор, пометьте функцию знаком@action
декоратор. Поскольку MobX свободен в ограничениях, просто коснитесь@observable
данные будут обновлены, но тогда произойдет злоупотреблениеПрямое обновление через статусОтследить процесс изменения данных непросто. Например, случайный в компоненте:this.props.todos.push(...)
, что запускает полное обновление данных приложения.
Поэтому MobX рекомендует использовать строгий режим, мы можем пройти только@action
Для изменения состояния используется функция оформления состояния, что согласуется с идеей изменения состояния через функцию действия в Redux, гарантируя, что изменения данных можно будет отслеживать и регулировать.
Еще одно отличие от Redux в том, что в MobX мы можем напрямую написатьрассчитанныйиз@computed
Логику данных, в Redux мы можем вычислить только вручную через полученное состояние, а в MobX через@computed
декоративныйget
Функция всегда может получить последнее вычисленное значение. Это также идея MOBX:Все, что получено из состояния приложения, должно быть получено автоматически..
Поскольку MobX не поддерживает единый источник данных, такой как Redux, нам очень просто расширять состояние, продолжать добавлять новые классы и инкапсулировать потребности.@observable
положение дел,@computed
Статус и соответствие обновлению данных@action
функция.
Процесс доступа к MobX в компонентах также намного проще, чем в Redux.
Первый — настроить в компоненте входа:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {
TodoListStore
} from './store'
import {
Provider
} from 'mobx-react'
// 状态
const rootStore = {
todoListStore: new TodoListStore()
}
// 通过 Provider/inject 来注入,react 16.8 + 可以使用 hooks + context 来注入
ReactDOM.render(
<Provider {...rootStore} >
<App />
</Provider>,
document.getElementById('root') as HTMLElement
);
Затем используйте в компонентах, которым необходимо использовать состояние MobX.mobx-react
в упаковкеobserver
а такжеinject
Функции высшего порядка вводят данные в:
App.tsx:
import React from 'react';
import {
observer,
inject
} from 'mobx-react'
import TodoAdd from './components/TodoAdd'
import TodoViewList from './components/TodoViewList'
import TodoCounter from './components/TodoCounter'
import './App.css';
import { IAppProp } from './interfaces'
// ! @inject 高阶方法注入会和 Typescript 有冲突,原因在于 TS 会检查外部调用传进来的 props 接口,
// ! 但是 inject 相当于中间注入,避开了检查,这就导致 TS 报错。
const App: React.FC<IAppProp> = inject('todoListStore')(observer(({ todoListStore }) => {
return (
<div className="app">
<div className="app-title">Classic React App ( mobx )</div>
<TodoAdd
addNewTodo={todoListStore!.addNewTodo}
/>
<TodoViewList
todos={todoListStore!.todos}
finishTodoById={todoListStore!.finishTodoById}
removeTodoById={todoListStore!.removeTodoById}
/>
<TodoCounter
finishedCount={todoListStore!.finishedTodoCount}
totalCount={todoListStore!.totalCount}
/>
</div>
);
}))
export default App;
TodoViewList.tsx:
import React from 'react'
import { observer } from 'mobx-react'
import TodoView from './TodoView'
import './styles/TodoViewList.css'
import {
IViewListProp
} from '../interfaces'
@observer
class TodoViewList extends React.Component<IViewListProp, {}> {
render() {
const {
removeTodoById,
finishTodoById,
todos
} = this.props
return (
<div className="viewlist">
{todos.map((item, index) => (
<TodoView
todo={item}
finishTodoById={finishTodoById}
removeTodoById={removeTodoById}
key={index}
/>
))}
</div>
)
}
}
export default TodoViewList
По сравнению со сложной концепцией Redux «компонент-контейнер», это намного проще, если оно используется в компонентах, которым требуется состояние MobX.@observer
Декоратор справится. просто и понятно.
7. React Hooks App with Redux
В выпуске React 16.8 представлен совершенно новый API-интерфейс Hooks, привносящий множество новых идей в экосистему React. Здесь мы видим, как написать одно и то же приложение с помощью Hooks + Redux, и в чем преимущества по сравнению с паттерном class + Redux.
Структура приложения:
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── src // 源码
│ ├── App.css
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── components
│ │ ├── TodoAdd.tsx
│ │ ├── TodoCounter.tsx
│ │ ├── TodoView.tsx
│ │ ├── TodoViewList.tsx
│ │ ├── assets
│ │ │ ├── delete.svg
│ │ │ ├── select.svg
│ │ │ └── select2.svg
│ │ └── styles
│ │ ├── TodoAdd.css
│ │ ├── TodoCounter.css
│ │ ├── TodoView.css
│ │ └── TodoViewList.css
│ ├── index.css
│ ├── index.tsx
│ ├── interfaces
│ │ ├── base.ts
│ │ └── index.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ └── store // redux store
│ ├── store.tsx
│ └── todo
│ ├── actions.ts
│ ├── index.ts
│ ├── reducers.ts
│ └── types.ts
├── tsconfig.json
└── yarn.lock
Мы обнаружили большую разницу: нет папки-контейнера по сравнению с классическим приложением React с Redux! То есть нам не нужно использовать контейнер для подключения к Redux.
Да, причина в том, что здесь мы используем Context API + Hooks API.
Контекстный API для замены оригиналаreact-redux
в сумкеProvider
компонент для глобального внедрения данных.Provider
Компонент также по сути использует React Context API, но раньше он не пользовался авторитетом у властей, а после выхода React 16 Context API получил официальное признание, и метод использования стал проще. Хуки, с другой стороны, имеют встроенную поддержку Redux, поэтому нет необходимости устанавливать дополнительные пакеты Redux.
На самом деле это приложение использует только собственные функции React и не устанавливает никаких внешних зависимостей:
"dependencies": {
"@types/jest": "24.0.15",
"@types/node": "12.6.8",
"@types/react": "16.8.23",
"@types/react-dom": "16.8.5",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1",
"typescript": "3.5.3"
}
Значение хуков заключается в том, что они могут совместно использовать бизнес-логику, поэтому основной файл в этом приложении находится в папке хранилища.store.tsx
:
import React from 'react'
import {
TodoReducerType,
TodoActionType,
initalTodoState,
todoReducer
} from './todo'
type combineDispatchsType = React.Dispatch<TodoActionType>
// 将所有的 dispatch 组合
const combineDispatchs = (dispatchs: Array<combineDispatchsType>) => (obj: TodoActionType) => {
for (let i = 0; i < dispatchs.length; i++) {
dispatchs[i](obj)
}
}
// 根组件状态
const AppState = {
todoState: [] as TodoReducerType,
dispatch: {} as ReturnType<typeof combineDispatchs>
}
// Context
export const ContextStore = React.createContext(AppState)
const HookContextProvider: React.FC = ({ children }) => {
const [todoState, todoDispatch] = React.useReducer(
todoReducer,
initalTodoState
)
return (
<ContextStore.Provider
value={{
todoState,
dispatch: combineDispatchs([
todoDispatch
])
}}
>
{children}
</ContextStore.Provider>
)
}
export default HookContextProvider
Этот файл делает следующие вещи:
- Определите AppState, содержащий состояние и действие
- Используйте AppState для инициализации React.createContext для создания глобального ContextStore.
- Используйте React.useReducer для создания определенных todoState, todoDispatch
- Внедрить todoState и todoDispatch во все приложение через компонент ContextStore.Provider.
можно увидеть,todoState
,todoDispatch
Это фокус всего кода, поскольку он содержит состояние и действие всего приложения, а состояние и действие генерируются.React.useReducer
Параметры в:todoReducer
,initalTodoState
, который находится в обычном режиме ReduxReducer
Функция, на самом деле, папка todo здесь — это папка todo перенесенного шаблона класса. Основные понятия все те же.
Это также показывает, что React впитал всю концепцию Redux через Hooks API.
И в компоненте нам тоже очень удобно пользоваться:
TodoViewList.tsx:
import React, {
useContext
} from 'react'
import TodoView from './TodoView'
import {
ContextStore
} from '../store/store'
import {
SELECT_TODO,
REMOVE_TODO
} from '../store/todo'
import './styles/TodoViewList.css'
const TodoViewList: React.FC = () => {
const {
todoState,
dispatch
} = useContext(ContextStore)
// 发起 action 操作
const selectTodoById = (id: number) => dispatch({
type: SELECT_TODO,
id
})
const removeTodoById = (id: number) => dispatch({
type: REMOVE_TODO,
id
})
return (
<div className="viewlist">
{todoState.map((item, index) => (
<TodoView
todo={item}
selectTodoById={selectTodoById}
removeTodoById={removeTodoById}
key={index}
/>
))}
</div>
)
}
export default TodoViewList
просто используйтеconst { todoState, dispatch } = useContext(ContextStore)
,Очень удобно.
8. React Hooks App with Mobx
По сравнению с хуками, которые поглощают Redux, MobX напрямую не цепляется и по-прежнему требует внешних зависимостей.
"dependencies": {
"@types/jest": "24.0.15",
"@types/node": "12.6.8",
"@types/react": "16.8.23",
"@types/react-dom": "16.8.5",
"mobx": "^5.13.0",
"mobx-react-lite": "^1.4.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1",
"typescript": "3.5.3"
},
Стоит отметить, чтоmobx-react-lite
Этот пакетmobx-react
Оптимизация заключается в том, чтобы следовать в хуки, то есть только в React 16.8+, а не в совместимом режиме. Согласно официальному заявлению, в конечном итоге будет включен пакет Lite.mobx-react
середина.
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── src // 源码
│ ├── App.css
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── components // UI 组件
│ │ ├── TodoAdd.tsx
│ │ ├── TodoCounter.tsx
│ │ ├── TodoView.tsx
│ │ ├── TodoViewList.tsx
│ │ ├── assets
│ │ │ ├── delete.svg
│ │ │ ├── select.svg
│ │ │ └── select2.svg
│ │ └── styles
│ │ ├── TodoAdd.css
│ │ ├── TodoCounter.css
│ │ ├── TodoView.css
│ │ └── TodoViewList.css
│ ├── index.css
│ ├── index.tsx
│ ├── interfaces
│ │ ├── base.ts
│ │ └── index.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ └── store // mobx store
│ ├── index.ts
│ └── useTodoStore.tsx
├── tsconfig.json
└── yarn.lock
Вот ключевой момент.Фокус всего приложения находится в папке магазина.useTodoStore.tsx
:
import React, {
useContext
} from 'react'
import {
useLocalStore
} from 'mobx-react-lite'
import {
observable
} from 'mobx'
import {
ITodo
} from '../interfaces'
// 定义 store shape
const createStore = () => ({
todos: [] as ITodo[], // 或者 Array<ITodo>
get finishedTodoCount() {
return this.todos.filter(todo => todo.finished).length
},
get totalCount() {
return this.todos.length
},
finishTodoById(id: number) {
this.todos.forEach(todo => {
if (todo.id === id) {
todo.finished = !todo.finished
}
})
},
addNewTodo(name: string) {
// *新增 observable Todo 对象
this.todos.push(observable({
id: Math.random(),
name: name,
finished: false
}))
},
removeTodoById(id: number) {
// 找到某一项的位置
const index = this.todos.findIndex(todo => todo.id === id)
this.todos.splice(index, 1)
}
})
type TTodoStore = ReturnType<typeof createStore>
// 创建 context
const TodoStoreContext = React.createContext<TTodoStore | null>(null)
// 创建 Provider,通过 React.Context 来注入
const TodoStoreProvider: React.FC = ({ children }) => {
const store = useLocalStore(createStore)
return (
<TodoStoreContext.Provider value={store}>
{children}
</TodoStoreContext.Provider>
)
}
// 创建 Hook
const useTodoStore = () => {
const store = useContext(TodoStoreContext)
if (!store) {
throw new Error('You have forgot to use StoreProvider, shame on you.')
}
return store
}
export {
TodoStoreProvider,
useTodoStore
}
здесь,TodoStoreProvider
Компоненты являются инкапсуляцией контекста, используяuseLocalStore
создает магазин MobX, аuseTodoStore
Крюк дляuseContext
Инкапсуляция, поэтому становится очень удобно использовать в компоненте:
App.tsx:
import React from 'react';
import './App.css';
import TodoAdd from './components/TodoAdd'
import TodoViewList from './components/TodoViewList'
import TodoCounter from './components/TodoCounter'
import {
TodoStoreProvider
} from './store'
const App: React.FC = () => {
return (
<TodoStoreProvider>
<div className="app">
<div className="app-title">React Hooks App ( mobx )</div>
<TodoAdd />
<TodoViewList />
<TodoCounter />
</div>
</TodoStoreProvider>
);
}
export default App;
TodoViewList.tsx:
import React from 'react'
import {
observer
} from 'mobx-react-lite'
import TodoView from './TodoView'
import './styles/TodoViewList.css'
import {
useTodoStore
} from '../store'
const TodoViewList: React.FC = observer(() => {
const todoStore = useTodoStore()
return (
<div className="viewlist">
{todoStore.todos.map((item, index) => (
<TodoView
todo={item}
finishTodoById={todoStore.finishTodoById}
removeTodoById={todoStore.removeTodoById}
key={index}
/>
))}
</div>
)
})
export default TodoViewList
Лично его использовать немного сложнее, чем пример приложения React Hooks с Redux, но намного проще, чем шаблон класса.useTodoStore
Его можно использовать в любом компоненте, если вы хотите расширить его позже, вы можете написать еще один.useXXXStore
.
9. Резюме
Запишите четыре примера, мои личные ощущения:
- Хуки действительно намного проще шаблона класса и являются будущей тенденцией стека технологий React.
- MobX не обязательно проще, чем Redux.Например, доступ к MobX через Hooks также немного проблематичен, и он не встроен напрямую в официальный API, как Redux.
- Функции редуктора и действия Redux можно безболезненно перенести в хуки из шаблона класса.
- Машинописный текст — это настоящая сила!