❤ отметьте меня, если вам нравится концепт ^_^
Преамбула
redux
,mobx
Это независимая структура управления состоянием, каждая со своим собственным абстрактным API, независимая от других сред пользовательского интерфейса (реагирование, vue...), в этой статье в основном рассказывается иreact
Эффект контраста используется в сочетании, поэтому следующие упомянутыеredux
,mobx
подразумеваемыйreact-redux
,mobx-react
Это позволяет имreact
библиотека привязки, которая работает вconcent
сам дляreact
Лично созданная среда разработки, управление потоком данных — это только одна из функций, а другие сопутствующие функции, улучшающие опыт разработки в ответ, могут использоваться по мере необходимости и будут удалены позже.concent
все сreact
Связанный частичный выпускconcent-core
, его позиционированиеredux
,mobx
аналогичный.
Итак, игроки, которые появятся в этой статье:
redux & react-redux
-
slogan
Контейнер состояния JavaScript для предсказуемого управления состоянием -
концепт дизайна
Единый источник правды, использующий чистые функции для изменения состояния
mobx & mobx-react
-
лозунг:
Простое, масштабируемое управление состоянием -
концепт дизайна
Все, что может быть получено из состояния приложения, должно быть получено
concent
-
лозунг:
Предсказуемое, прогрессивное, высокопроизводительное решение для реактивной разработки с нулевым вторжением -
концепт дизайна
Я считаю, что метод разработки, объединяющий неизменяемость + сбор зависимостей, — это будущее реакции, улучшение характеристик компонентов реакции, меньше написания и больше работы.
После ознакомления с предысторией троих наша сцена официально передается им, и начинается раунд соревнования.Кто в итоге станет вашим фаворитом?
Предварительный просмотр результата
В следующих пяти раундах конкурса есть много реальных демонстрационных кодов.Результаты сравнения сообщаются здесь заранее, чтобы читатели могли быстро понять.
конфигурация магазина | concent | mbox | redux |
---|---|---|---|
поддержка разделения | Yes | Yes | No |
Нет корневого провайдера, и использование не требует явного импорта | Yes | No | No |
редукторthis
|
Yes | No | Yes |
Храните данные или методы без ручного сопоставления с компонентами | Yes | Yes | No |
пример редукционного счетчика
пример счетчика mobx
Пример счетчика концентрации
изменение состояния | concent | mbox | redux |
---|---|---|---|
По принципу неизменности | Yes | No | Yes |
кратчайшая ссылка | Yes | Yes | No |
источник пользовательского интерфейса можно отследить | Yes | No | No |
не это | Yes | No | Yes |
Атомарные коммиты разделения и слияния | Да (ленивый) | Да (на основе транзакции) | No |
Коллекция зависимостей | concent | mbox | redux |
---|---|---|---|
Поддержка сбора зависимостей во время выполнения | Yes | Yes | No |
Точный рендеринг | Yes | Yes | No |
не это | Yes | No | No |
Всего один шаг API | Yes | No | No |
пример мобкса
Пример содержания
производные данные | concent | mbox | redux(reselect) |
---|---|---|---|
Автоматическое поддержание зависимостей между результатами расчета | Yes | Yes | No |
Собирать зависимости при запуске чтения результатов расчета | Yes | Yes | No |
Функция расчета без этого | Yes | No | Yes |
Пример вычисления редукса
пример вычислений mobx
Пример вычисляемого содержания
todo-mvc бой
redux todo-mvc
mobx todo-mvc
concent todo-mvc
раунд 1 - первый опыт работы со стилем кода
Как красивый мальчик в демонстрационном мире, счетчик выдвигался на сцену бессчетное количество раз. На этот раз мы все еще не исключение. Приходите к счетчику, чтобы испытать рутину разработки трех фреймворков. Используются следующие три версии.create-react-app
создан, и смногомодульный подходОрганизовывать код и стремиться быть ближе к сценарию кода реальной среды.
редукс (действие, редуктор)
пройти черезmodels
Разделите функции по модулям на разные редюсеры, структура каталогов следующая
|____models # business models
| |____index.js # 暴露store
| |____counter # counter模块相关的action、reducer
| | |____action.js
| | |____reducer.js
| |____ ... # 其他模块
|____CounterCls # 类组件
|____CounterFn # 函数组件
|____index.js # 应用入口文件
Код здесь организован только с помощью оригинального шаблона redux.На самом деле многие разработчики могут выбрать
rematch
,dva
Фреймворк для вторичной инкапсуляции и улучшенного написания на основе редукса, но это не мешает нам понимать инстанс счетчика.
Построить счетчикaction
// code in models/counter/action
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
export const increase = number => {
return { type: INCREMENT, payload: number };
};
export const decrease = number => {
return { type: DECREMENT, payload: number };
};
Построить счетчикreducer
// code in models/counter/reducer
import { INCREMENT, DECREMENT } from "./action";
export default (state = { count: 0 }, action) => {
const { type, payload } = action;
switch (type) {
case INCREMENT:
return { ...state, count: state.count + payload };
case DECREMENT:
return { ...state, count: state.count - payload };
default:
return state;
}
};
сливатьсяreducer
структураstore
, и внедряется в корневой компонент
mport { createStore, combineReducers } from "redux";
import countReducer from "./models/counter/reducer";
const store = createStore(combineReducers({counter:countReducer}));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
используйте соединение, чтобы соединить пользовательский интерфейс сstore
import React from "react";
import { connect } from "react-redux";
import { increase, decrease } from "./redux/action";
@connect(
state => ({ count: state.counter.count }),// mapStateToProps
dispatch => ({// mapDispatchToProps
increase: () => dispatch(increase(1)),
decrease: () => dispatch(decrease(1))
}),
)
class Counter extends React.Component {
render() {
const { count, increase, decrease } = this.props;
return (
<div>
<h1>Count : {count}</h1>
<button onClick={increase}>Increase</button>
<button onClick={decrease}>decrease</button>
</div>
);
}
}
export default Counter;
В приведенном выше примере записывается компонент класса, и на данный момент горячийhook
,redux v7
Также выпущен соответствующий APIuseSelector
,useDispatch
import * as React from "react";
import { useSelector, useDispatch } from "react-redux";
import * as counterAction from "models/counter/action";
const Counter = () => {
const count = useSelector(state => state.counter.count);
const dispatch = useDispatch();
const increase = () => dispatch(counterAction.increase(1));
const decrease = () => dispatch(counterAction.decrease(1));
return (
<>
<h1>Fn Count : {count}</h1>
<button onClick={increase}>Increase</button>
<button onClick={decrease}>decrease</button>
</>
);
};
export default Counter;
Отрисовывая эти два счетчика,Посмотрите пример редукса
function App() {
return (
<div className="App">
<CounterCls/>
<CounterFn/>
</div>
);
}
mobx(store, inject)
Когда в приложении есть несколько хранилищ (здесь мы можем понимать хранилище как блок редуктора в redux, который агрегирует данные, извлекает данные и изменяет поведение), есть много способов получить хранилища mobx, например, прямой доступ, где это необходимо. , Ввести в переменные-члены
import someStore from 'models/foo';// 是一个已经实例化的store实例
@observer
class Comp extends React.Component{
foo = someStore;
render(){
this.foo.callFn();//调方法
const text = this.foo.text;//取数据
}
}
Здесь мы следуем общепринятой лучшей практике, то есть объединяем все хранилища в корневое хранилище и вешаем его на Provider, оборачиваем Provider во весь корневой компонент приложения и отмечаем место, где он используется.inject
Достаточно декоратора, наша структура каталогов выглядит следующим образом, иredux
версия не отличается
|____models # business models
| |____index.js # 暴露store
| |____counter # counter模块相关的store
| | |____store.js
| |____ ... # 其他模块
|____CounterCls # 类组件
|____CounterFn # 函数组件
|____index.js # 应用入口文件
Построить счетчикstore
import { observable, action, computed } from "mobx";
class CounterStore {
@observable
count = 0;
@action.bound
increment() {
this.count++;
}
@action.bound
decrement() {
this.count--;
}
}
export default new CounterStore();
объединить всеstore
для根store
, и внедряется в корневой компонент
// code in models/index.js
import counter from './counter';
import login from './login';
export default {
counter,
login,
}
// code in index.js
import React, { Component } from "react";
import { render } from "react-dom";
import { Provider } from "mobx-react";
import store from "./models";
import CounterCls from "./CounterCls";
import CounterFn from "./CounterFn";
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Создайте компонент класса
import React, { Component } from "react";
import { observer, inject } from "mobx-react";
@inject("store")
@observer
class CounterCls extends Component {
render() {
const counter = this.props.store.counter;
return (
<div>
<div> class Counter {counter.count}</div>
<button onClick={counter.increment}>+</button>
<button onClick={counter.decrement}>-</button>
</div>
);
}
}
export default CounterCls;
Создайте функциональный компонент
import React from "react";
import { useObserver, observer } from "mobx-react";
import store from "./models";
const CounterFn = () => {
const { counter } = store;
return useObserver(() => (
<div>
<div> class Counter {counter.count}</div>
<button onClick={counter.increment}>++</button>
<button onClick={counter.decrement}>--</button>
</div>
));
};
export default CounterFn;
Отобразите эти два счетчика,Посмотрите пример mobx
function App() {
return (
<div className="App">
<CounterCls/>
<CounterFn/>
</div>
);
}
concent(reducer, register)
Concent, как и redux, имеет одно глобальное корневое состояние.RootStore
, ключ первого уровня в корневом состоянии используется в качестве пространства имен модуля, и модуль согласия должен быть настроенstate
,остатокreducer
,computed
,watch
,init
Он необязателен и может быть настроен по мере необходимости.Если все модули магазина пишутся в одном месте, самый простой вариантconcent
Примеры следующие
import { run, setState, getState, dispatch } from 'concent';
run({
counter:{// 配置counter模块
state: { count: 0 }, // 【必需】定义初始状态, 也可写为函数 ()=>({count:0})
// reducer: { ...}, // 【可选】修改状态的方法
// computed: { ...}, // 【可选】计算函数
// watch: { ...}, // 【可选】观察函数
// init: { ...}, // 【可选】异步初始化状态函数
}
});
const count = getState('counter').count;// count is: 0
// count is: 1,如果有组件属于该模块则会被触发重渲染
setState('counter', {count:count + 1});
// 如果在了counter.reducer下定义了changeCount方法
// dispatch('counter/changeCount')
запускатьconcent
После загрузки магазина его можно зарегистрировать в любом другом компоненте класса или функциональном компоненте, чтобы он принадлежал указанному модулю или соединял несколько модулей.
import { useConcent, register } from 'concent';
function FnComp(){
const { state, setState, dispatch } = useConcent('counter');
// return ui ...
}
@register('counter')
class ClassComp extends React.Component(){
render(){
const { state, setState, dispatch } = this.ctx;
// return ui ...
}
}
Тем не менее, рекомендуется размещать параметры определения модуля в каждом файле, чтобы добиться эффекта четких обязанностей и разделения задач, поэтому для счетчика структура каталогов выглядит следующим образом.
|____models # business models
| |____index.js # 配置store各个模块
| |____counter # counter模块相关
| | |____state.js # 状态
| | |____reducer.js # 修改状态的函数
| | |____index.js # 暴露counter模块
| |____ ... # 其他模块
|____CounterCls # 类组件
|____CounterFn # 函数组件
|____index.js # 应用入口文件
|____runConcent.js # 启动concent
Построить счетчикstate
а такжеreducer
// code in models/counter/state.js
export default {
count: 0,
}
// code in models/counter/reducer.js
export function increase(count, moduleState) {
return { count: moduleState.count + count };
}
export function decrease(count, moduleState) {
return { count: moduleState.count - count };
}
Два способа настройки магазина
- Настраивается в функции запуска
import counter from 'models/counter';
run({counter});
- пройти через
configure
конфигурация интерфейса,run
Интерфейс отвечает только за запуск концентрата
// code in runConcent.js
import { run } from 'concent';
run();
// code in models/counter/index.js
import state from './state';
import * as reducer from './reducer';
import { configure } from 'concent';
configure('counter', {state, reducer});// 配置counter模块
Создайте функциональный компонент
import * as React from "react";
import { useConcent } from "concent";
const Counter = () => {
const { state, dispatch } = useConcent("counter");
const increase = () => dispatch("increase", 1);
const decrease = () => dispatch("decrease", 1);
return (
<>
<h1>Fn Count : {state.count}</h1>
<button onClick={increase}>Increase</button>
<button onClick={decrease}>decrease</button>
</>
);
};
export default Counter;
Этот функциональный компонент мы следуем традиционномуhook
стиль записи, то есть каждый рендеринг выполняетhook
функция, используяhook
Базовый интерфейс, возвращаемый функцией, снова определяет функцию действия, соответствующую текущим бизнес-требованиям.
Но так как содержание обеспечиваетsetup
интерфейс, мы можем воспользоваться его способностью выполняться только один раз перед начальным рендерингом и поместить эти функции действия вsetup
Внутренне определен как статическая функция, чтобы избежать повторных определений, поэтому лучше использовать функциональный компонент.
import * as React from "react";
import { useConcent } from "concent";
export const setup = ctx => {
return {
// better than ctx.dispatch('increase', 1);
increase: () => ctx.moduleReducer.increase(1),
decrease: () => ctx.moduleReducer.decrease(1)
};
};
const CounterBetter = () => {
const { state, settings } = useConcent({ module: "counter", setup });
const { increase, decrease } = settings;
// return ui...
};
export default CounterBetter;
Создайте компонент класса, повторно используйтеsetup
логика в
import React from "react";
import { register } from "concent";
import { setup } from './CounterFn';
@register({module:'counter', setup})
class Counter extends React.Component {
render() {
// this.state 和 this.ctx.state 取值效果是一样的
const { state, settings } = this.ctx;
// return ui...
}
}
export default Counter;
Отобразите эти два счетчика,Посмотрите пример концентрата
function App() {
return (
<div className="App">
<CounterCls />
<CounterFn />
</div>
);
}
Обзор и резюме
Этот раунд показывает различную организацию кода и структуру 3 пар фреймворков при определении многомодульного состояния.
-
redux
пройти черезcombineReducers
СотрудничатьProvider
Оберните корневой компонент, одновременно получая рукописный вводmapStateToProps
а такжеmapActionToProps
Чтобы помочь компоненту получить данные и методы хранилища -
mobx
путем объединения несколькихsubStore
Кstore
объект и подходитProvider
Обертывая корневой компонент, данные и методы хранилища можно получить напрямую. -
concent
пройти черезrun
Централизованная конфигурация интерфейса илиconfigure
Конфигурация разделения интерфейса, данные и методы хранения могут быть получены напрямую
конфигурация магазина | concent | mbox | redux |
---|---|---|---|
поддержка разделения | Yes | Yes | No |
Нет корневого провайдера и нет необходимости явно импортировать, где он используется | Yes | No | No |
редукторthis
|
Yes | No | Yes |
Храните данные или методы без ручного сопоставления с компонентами | Yes | Yes | No |
раунд 2 - изменение состояния
Стили модификации трех фреймворков совершенно разные.redux
Существуют строгие ограничения на методы модификации состояния, поэтому все варианты поведения модификации состояния должны быть отправлены.action
, затем нажмите соответствующийreducer
Синтезировать новые состояния.
mobx
Он способен реагировать и может быть изменен напрямую, но также вызывает проблемы с неотслеживаемыми методами модификации данных, что приводит кmobx-state-tree
чтобы соответствовать ограничениям для изменения поведения данных.
concent
Изменения полностью соблюденыreact
запись модификацииsetState
стиль, а затем инкапсулировать на этой основеdispatch
,invoke
,sync
Ряд API-интерфейсов, и независимо от того, какой API-интерфейс вызывается, может не только отслеживать полную связь модификации данных, но также включать источник запуска модификации данных.
redux(dispatch)
синхронное действие
export const changeFirstName = firstName => {
return {
type: CHANGE_FIRST_NAME,
payload: firstName
};
};
Асинхронное действие, с помощьюredux-thunk
Что нужно сделать
// code in models/index.js, 配置thunk中间件
import thunk from "redux-thunk";
import { createStore, combineReducers, applyMiddleware } from "redux";
const store = createStore(combineReducers({...}), applyMiddleware(thunk));
// code in models/login/action.js
export const CHANGE_FIRST_NAME = "CHANGE_FIRST_NAME";
const delay = (ms = 1000) => new Promise(r => setTimeout(r, ms));
// 工具函数,辅助写异步action
const asyncAction = asyncFn => {
return dispatch => {
asyncFn(dispatch).then(ret => {
if(ret){
const [type, payload] = ret;
dispatch({ type, payload });
}
}).catch(err=>alert(err));
};
};
export const asyncChangeFirstName = firstName => {
return asyncAction(async (dispatch) => {//可用于中间过程多次dispatch
await delay();
return [CHANGE_FIRST_NAME, firstName];
});
};
версия mobx (это.XXX)
Синхронные и асинхронные действия
import { observable, action, computed } from "mobx";
const delay = (ms = 1000) => new Promise(r => setTimeout(r, ms));
class LoginStore {
@observable firstName = "";
@observable lastName = "";
@action.bound
changeFirstName(firstName) {
this.firstName = firstName;
}
@action.bound
async asyncChangeFirstName(firstName) {
await delay();
this.firstName = firstName;
}
@action.bound
changeLastName(lastName) {
this.lastName = lastName;
}
}
export default new LoginStore();
Изменить напрямую
const LoginFn = () => {
const { login } = store;
const changeFirstName = e => login.firstName = e.target.value;
// ...
}
Изменить действием
const LoginFn = () => {
const { login } = store;
const const changeFirstName = e => login.changeFirstName(e.target.value);
// ...
}
concent(dispatch,setState,invoke,sync)
Контент больше не различаетaction
а такжеreducer
, пользовательский интерфейс вызывает напрямуюreducer
метод, в то время какreducer
Метод может быть синхронным или асинхронным и поддерживает любую комбинацию друг друга и ленивых вызовов, что значительно снижает умственную нагрузку разработчиков.
Синхронизироватьreducer
с асинхроннымreducer
// code in models/login/reducer.js
const delay = (ms = 1000) => new Promise(r => setTimeout(r, ms));
export function changeFirstName(firstName) {
return { firstName };
}
export async function asyncChangeFirstName(firstName) {
await delay();
return { firstName };
}
export function changeLastName(lastName) {
return { lastName };
}
Редуктор, который можно комбинировать произвольно, методы, принадлежащие одному и тому же модулю, можно вызывать напрямую на основе ссылки на метод, и функция редуктора не обязана возвращать новое состояние фрагмента, также возможно комбинировать только другие редьюсеры.
// reducerFn(payload:any, moduleState:{}, actionCtx:IActionCtx)
// 当lazy调用此函数时,任何一个函数出错了,中间过程产生的所有状态都不会提交到store
export async changeFirstNameAndLastName([firstName, lastName], m, ac){
await ac.dispatch(changeFirstName, firstName);
await ac.dispatch(changeFirstName, lastName);
// return {someNew:'xxx'};//可选择此reducer也返回新的片断状态
}
// 视图处
function UI(){
const ctx useConcent('login');
// 触发两次渲染
const normalCall = ()=>ctx.mr.changeFirstNameAndLastName(['first', 'last']);
// 触发一次渲染
const lazyCall = ()=>ctx.mr.changeFirstNameAndLastName(['first', 'last'], {lazy:true});
return (
<>
<button onClick={handleClick}> normalCall </button>
<button onClick={handleClick}> lazyCall </button>
</>
)
}
Неленивый процесс вызова
ленивый процесс вызова
Конечно, кромеreducer
, остальные три метода могут быть согласованы произвольно, иreducer
иметь такое же состояние синхронизации с другими экземплярами, которые принадлежат тому же модулю и зависят от состояния
- setState
function FnUI(){
const {setState} = useConcent('login');
const changeName = e=> setState({firstName:e.target.name});
// ... return ui
}
@register('login')
class ClsUI extends React.Component{
changeName = e=> this.setState({firstName:e.target.name})
render(){...}
}
- invoke
function _changeName(firstName){
return {firstName};
}
function FnUI(){
const {invoke} = useConcent('login');
const changeName = e=> invoke(_changeName, e.target.name);
// ... return ui
}
@register('login')
class ClsUI extends React.Component{
changeName = e=> this.ctx.invoke(_changeName, e.target.name)
render(){...}
}
- sync
Дополнительные сведения о синхронизации см. в файле App2-1-sync.js.
function FnUI(){
const {sync, state} = useConcent('login');
return <input value={state.firstName} onChange={sync('firstName')} />
}
@register('login')
class ClsUI extends React.Component{
changeName = e=> this.ctx.invoke(_changeName, e.target.name)
render(){
return <input value={this.state.firstName} onChange={this.ctx.sync('firstName')} />
}
}
Помните, мы упомянули об этом, чтобы сконцентрироваться, прежде чем началось сравнение 2 раунда:Возможность проследить не только полную связь модификации данных, но и источник запуска модификации данных, что это значит, потому что каждый компонент содержания имеетctx
иметь уникальный идентификаторccUniqueKey
Идентифицирует текущий экземпляр компонента, который{className}_{randomTag}_{seq}
Генерируется автоматически, то есть имя класса (не указан тип компонента?CClass
, ?CCFrag
, ?CCHook
) плюс случайная метка плюс автоматически увеличивающийся серийный номер, если вы хотите намеренно отслеживать и изменять исходный пользовательский интерфейс, вам необходимо поддерживать его вручнуюtag
,ccClassKey
Да, а потомconcent-plugin-redux-devtoolдля достижения нашей цели.
function FnUI(){
const {sync, state, ccUniqueKey} = useConcent({module:'login', tag:'xxx'}, 'FnUI');
// tag 可加可不加,
// 不加tag,ccUniqueKey形如: FnUI_xtst4x_1
// 加了tag,ccUniqueKey形如: FnUI_xxx_1
}
@register({module:'login', tag:'yyy'}, 'ClsUI')
class ClsUI extends React.Component{...}
доступconcent-plugin-redux-devtool
После этого вы увидите, что любая модификация действия Action будет содержать полеccUniqueKey
.
Обзор и резюме
В этом раунде мы провели всестороннее сравнение методов модификации данных, чтобы разработчики могли понятьconcent
С точки зрения опыта кодирования разработчика, большие усилия были предприняты во всех аспектах.
Для метода обновления статуса сравнитеredux
, когда все наши процессы действия сжаты до кратчайшего, нет действия-->редьюсер такого звена, не важно выделяется функция хранения или функция побочного эффекта(rematch
,dva
и т.п. извлеченные понятия), отдайте эти понятияjs
Сам синтаксис будет более удобным и понятным, если вам нужны чистые функции, просто напишитеexport function
, вам нужно написать функцию побочного эффектаexport async function
.
В сравненииmobx
, все это базовая функция, которую можно разобрать в любом сочетании, нетthis
, полностью ориентированный на ФП, дающийinput
ожидалoutput
, этот метод также более удобен для тестового контейнера.
изменение состояния | concent | mbox | redux |
---|---|---|---|
По принципу неизменности | Yes | No | Yes |
кратчайшая ссылка | Yes | Yes | No |
источник пользовательского интерфейса можно отследить | Yes | No | No |
не это | Yes | No | Yes |
Атомарные коммиты разделения и слияния | Да (ленивый) | Да (на основе транзакции) | No |
раунд 3 - сбор зависимостей
Этот раунд является очень тяжелой ссылкой, полагаясь на коллекцию, что позволяет рендерингу пользовательского интерфейса поддерживать минимальный диапазон обновлений, то есть точные обновления, поэтомуvue
Некоторые аспекты теста победятreact
, когда мыreact
Посмотрите, что еще интересного произойдет после того, как вы наденете крылья, полагаясь на коллекцию.
начать говорить снова依赖收集
Прежде, давайте резюмируемreact
Оригинальный механизм рендеринга, когда состояние компонента меняется, если его пользовательский компонент не поддерживается вручнуюshouldComponentUpdate
При оценке он всегда будет отображать все сверху вниз, аredux
изcconnect
интерфейс берет на себяshouldComponentUpdate
Поведение, когда действие запускает модификацию действия, все подключенные компоненты будутmapStateToProps
Полученное состояние и текущая датаmapStateToProps
Результирующее состояние поверхностно сравнивается, чтобы решить, следует ли обновлять завернутые дочерние компоненты.
В эпоху крюков это обеспечиваетReact.memo
Пользователям необходимо блокировать такие «совместно связанные» обновления, но пользователи обязаны пройтиprimitive
тип данных или неизменная ссылка наprops
,иначеReact.memo
Неглубокое сравнение вернет false.
ноredux
Одна проблема заключается в том, что если определенное состояние больше не используется в представлении в определенный момент, оно должно не рендериться, а рендериться,mobx
Концепция переноса наименьшего подмножества данных, полученных пользовательским интерфейсом во время выполнения, элегантно решает эту проблему, ноconcent
Идя дальше, сокрытие поведения коллекции зависимостей является более элегантным, и пользователю не нужно знатьobservable
и других связанных терминов и понятий, определенное значение рендеринга, которое вы принимаете, имеет точку зависимости от этого значения, а следующее рендеринг не имеет определенного значения.stateKey
Поведение значения должно быть удалено из зависимости, котораяvue
молодец, что сделалreact
Имеет более элегантные, более комплексные механизмы сбора зависят,concent
Также было приложено немало усилий.
редукционная версия (не поддерживается)
Разрешение сбора зависимостей неredux
Первоначальное намерение его рождения, здесь мы можем только молча пригласить его в область кандидатов и участвовать в следующем раунде конкурса.
версия mobx (вычисляется, useObserver)
с помощью декоратора илиdecorate
Функция помечает свойство, которое нужно наблюдать или вычислять.
import { observable, action, computed } from "mobx";
const delay = (ms = 1000) => new Promise(r => setTimeout(r, ms));
class LoginStore {
@observable firstName = "";
@observable lastName = "";
@computed
get fullName(){
return `${this.firstName}_${this.lastName}`
}
@computed
get nickName(){
return `${this.firstName}>>nicknick`
}
@computed
get anotherNickName(){
return `${this.nickName}_another`
}
}
export default new LoginStore();
Зависимость возникает, когда в пользовательском интерфейсе используется статус наблюдения или результат расчета.
- Зависит только от результата расчета, метода записи компонента класса
@inject("store")
@observer
class LoginCls extends Component {
state = {show:true};
toggle = ()=> this.setState({show:!this.state.show})
render() {
const login = this.props.store.login;
return (
<>
<h1>Cls Small Comp</h1>
<button onClick={this.toggle}>toggle</button>
{this.state.show ? <div> fullName:{login.fullName}</div>: ""}
</>
)
}
}
- Зависит только от результата расчета, функциональная составляющая записывается
import { useObserver } from "mobx-react";
// show为true时,当前组件读取了fullName,
// fullName由firstName和lastName计算而出
// 所以他的依赖是firstName、lastName
// 当show为false时,当前组件无任何依赖
export const LoginFnSmall = React.memo((props) => {
const [show, setShow] = React.useState(true);
const toggle = () => setShow(!show);
const { login } = store;
return useObserver(() => {
return (
<>
<h1>Fn Small Comp</h1>
<button onClick={toggle}>toggle</button>
{show ? <div> fullName:{login.fullName}</div>: ""}
</>
)
});
});
Нет никакой разницы между зависимостью от состояния и зависимостью от результата вычисления.this.props.login
Получение соответствующих результатов по вышеизложенному приводит к зависимости пользовательского интерфейса от данных.
concent(state,moduleComputed)
Никаких декораторов не нужно отмечать наблюдаемые свойства и вычисленные результаты, простоjson
объекты и функции, фаза выполнения автоматически преобразуется вProxy
объект.
Результат расчета зависит от
// code in models/login/computed.js
// n: newState, o: oldState, f: fnCtx
// fullName的依赖是firstName lastName
export function fullName(n, o, f){
return `${n.firstName}_${n.lastName}`;
}
// nickName的依赖是firstName
export function nickName(n, o, f){
return `${n.firstName}>>nicknick`
}
// anotherNickName基于nickName缓存结果做二次计算,而nickName的依赖是firstName
// 所以anotherNickName的依赖是firstName,注意需将此函数放置到nickName下面
export function anotherNickName(n, o, f){
return `${f.cuVal.nickName}_another`;
}
- Зависит только от результата расчета, метода записи компонента класса
@register({ module: "login" })
class _LoginClsSmall extends React.Component {
state = {show:true};
render() {
const { state, moduleComputed: mcu, syncBool } = this.ctx;
// show为true时实例的依赖为firstName+lastName
// 为false时,则无任何依赖
return (
<>
<h1>Fn Small Comp</h1>
<button onClick={syncBool("show")}>toggle</button>
{state.show ? <div> fullName:{mcu.fullName}</div> : ""}
</>
);
}
}
- Зависит только от результата расчета, функциональная составляющая записывается
export const LoginFnSmall = React.memo(props => {
const { state, moduleComputed: mcu, syncBool } = useConcent({
module: "login",
state: { show: true }
});
return (
<>
<h1>Fn Small Comp</h1>
<button onClick={syncBool("show")}>toggle</button>
{state.show ? <div> fullName:{mcu.fullName}</div> : ""}
</>
);
});
а такжеmobx
Точно так же нет разницы между зависимостью от состояния и зависимостью от результата расчета.ctx.state
Получение соответствующих результатов на вышеизложенном производит зависимость пользовательского интерфейса от данных, и каждый рендерингconcent
Все динамически собирают последние зависимости текущего экземпляра, в экземпляреdidUpdate
Фазы перемещают зависимости, которые ушли.
- зависимости жизненного цикла
concent
Функции жизненного цикла компонентов класса и функциональных компонентов унифицированы в архитектуре, поэтому при изменении определенного состояния будут запущены функции жизненного цикла, которые зависят от него, и поддерживают классы и функции для совместного использования этой логики.
export const setupSm = ctx=>{
// 当firstName改变时,组件渲染渲染完毕后会触发
ctx.effect(()=>{
console.log('fisrtName changed', ctx.state.fisrtName);
}, ['firstName'])
}
// 类组件里使用
export const LoginFnSmall = React.memo(props => {
console.log('Fn Comp ' + props.tag);
const { state, moduleComputed: mcu, sync } = useConcent({
module: "login",setup: setupSm, state: { show: true }
});
//...
}
// 函数组件里使用
@register({ module: "login", setup:setupSm })
class _LoginClsSmall extends React.Component {...}
Обзор и резюме
В этом раунде сбора зависимостейconcent
Форма набора зависимостей и форма представления компонентов , иmobx
Разница очень большая, и весь процесс сбора зависимостей не задействует никаких других избыточных API, иmbox
требуетсяcomputed
Измените поле Getter, которое необходимо использовать в компоненте функцииuseObserver
Статус пакета возвращается в пользовательский интерфейс,concent
Обратите больше внимания на все это функция, устраняющаяthis
это ключевое слово, используяfnCtx
Контекст функции передает вычисленные результаты, явно различаяstate
а такжеcomputed
держит объект-контейнер.
Коллекция зависимостей | concent | mbox | redux |
---|---|---|---|
Поддержка сбора зависимостей во время выполнения | Yes | Yes | No |
Точный рендеринг | Yes | Yes | No |
не это | Yes | No | No |
Всего один шаг API | Yes | No | No |
раунд 4 - производные данные
все еще помнюmobx
лозунг? Все, что может быть получено из состояния приложения, должно быть получено, выявляя проблему, которая действительно существует, и мы не можем избежать.Большая часть состояния приложения передается пользовательскому интерфейсу для использования до того, как оно будет сопровождаться процессом вычисления, результат вычисления мы называем производным. данные.
мы все это знаемvue
Эта концепция была встроена, открывая необязательныйcomputed
для обработки вычислений и кэширования полученных данных,react
Нет такого понятия,redux
не дает такой возможности, ноredux
Открытый механизм промежуточного программного обеспечения позволяет сообществу найти точку входа для поддержки этой возможности, поэтому здесь мы сосредоточимся наredux
Когда дело доходит до вычислений, я имею в виду то, что де-факто стало популярной стандартной библиотекой.reslect
.
mobx
а такжеconcent
У всех есть собственная вычислительная поддержка, мы в вышеуказанномКоллекция зависимостейпродемонстрировано в раундеmobx
а такжеconcent
Производный код данных , поэтому этот раунд только дляredux
Пример записи производных данных
redux(reselect)
последний релиз редуксаv7
версия, которая предоставляет два API,useDispatch
а такжеuseSelector
, использование следует предыдущемуmapStateToState
а такжеmapDispatchToProps
Полностью эквивалентно, наш пример будет использовать как компоненты класса, так и компоненты функции для демонстрации.
определить селектор
import { createSelector } from "reselect";
// getter,仅用于取值,不参与计算
const getFirstName = state => state.login.firstName;
const getLastName = state => state.login.lastName;
// selector,等同于computed,手动传入计算依赖关系
export const selectFullName = createSelector(
[getFirstName, getLastName],
(firstName, lastName) => `${firstName}_${lastName}`
);
export const selectNickName = createSelector(
[getFirstName],
(firstName) => `${firstName}>>nicknick`
);
export const selectAnotherNickName = createSelector(
[selectNickName],
(nickname) => `${nickname}_another`
);
Компонент класса получает селектор
import React from "react";
import { connect } from "react-redux";
import * as loginAction from "models/login/action";
import {
selectFullName,
selectNickName,
selectAnotherNickName
} from "models/login/selector";
@connect(
state => ({
firstName: state.login.firstName,
lastName: state.login.lastName,
fullName: selectFullName(state),
nickName: selectNickName(state),
anotherNickName: selectAnotherNickName(state),
}), // mapStateToProps
dispatch => ({
// mapDispatchToProps
changeFirstName: e =>
dispatch(loginAction.changeFirstName(e.target.value)),
asyncChangeFirstName: e =>
dispatch(loginAction.asyncChangeFirstName(e.target.value)),
changeLastName: e => dispatch(loginAction.changeLastName(e.target.value))
})
)
class Counter extends React.Component {
render() {
const {
firstName,
lastName,
fullName,
nickName,
anotherNickName,
changeFirstName,
asyncChangeFirstName,
changeLastName
} = this.props;
return 'ui ...'
}
}
export default Counter;
Функциональный компонент получает селектор
import * as React from "react";
import { useSelector, useDispatch } from "react-redux";
import * as loginAction from "models/login/action";
import {
selectFullName,
selectNickName,
selectAnotherNickName
} from "models/login/selector";
const Counter = () => {
const { firstName, lastName } = useSelector(state => state.login);
const fullName = useSelector(selectFullName);
const nickName = useSelector(selectNickName);
const anotherNickName = useSelector(selectAnotherNickName);
const dispatch = useDispatch();
const changeFirstName = (e) => dispatch(loginAction.changeFirstName(e.target.value));
const asyncChangeFirstName = (e) => dispatch(loginAction.asyncChangeFirstName(e.target.value));
const changeLastName = (e) => dispatch(loginAction.changeLastName(e.target.value));
return 'ui...'
);
};
export default Counter;
Пример онлайн-данных, полученных с помощью Redux
mobx (вычисляемый декоратор)
См. приведенный выше пример кода для сбора зависимостей, который здесь повторяться не будет.
concent (полученный непосредственно модулем Computed)
См. приведенный выше пример кода для сбора зависимостей, который здесь повторяться не будет.
Обзор и резюме
в сравнении сmobx
непосредственно изthis.pops.someStore
Получать,concent
непосредственно изctx.moduleComputed
Существует дополнительный процесс ручного ведения расчетных зависимостей или сопоставления и выбора результатов.Я думаю, с первого взгляда понятно, какой метод разработчики более охотно используют.
производные данные | concent | mbox | redux(reselect) |
---|---|---|---|
Автоматическое поддержание зависимостей между результатами расчета | Yes | Yes | No |
Собирать зависимости при запуске чтения результатов расчета | Yes | Yes | No |
Функция расчета без этого | Yes | No | Yes |
раунд 5 - Настоящий TodoMvc
Вышеупомянутые 4 раунда объединяют яркие примеры кода, обобщают характеристики и стили кодирования трех фреймворков. Я полагаю, что читатели ожидают увидеть примеры кода, которые ближе к производственной среде, чтобы увидеть различия, поэтому давайте, наконец, использоватьTodoMvc
Приходите к концу этого конкурса функций, я надеюсь на ваше понимание и опытconcent
, нанеизменный & Коллекция зависимостейТур по программированию на React.
redux-todo-mvc
Посмотрите демонстрацию redux-todo-mvc
связанный с действием
связанный с редуктором
вычисленный
mobx-todo-mvc
связанный с действием
вычисленный
concent-todo-mvc
Ознакомьтесь с демо-версией концентрата todo-mvc.
связанный с редуктором
вычисленный связанный
end
Наконец, давайте использоватьМинимальная версия приложения concentВ завершение этой статьи скажите, выберете ли вы в будущем концентрат в качестве своего оружия разработки реакции?
import React from "react";
import "./styles.css";
import { run, useConcent, defWatch } from 'concent';
run({
login:{
state:{
name:'c2',
addr:'bj',
info:{
sex: '1',
grade: '19',
}
},
reducer:{
selectSex(sex, moduleState){
const info = moduleState.info;
info.sex = sex;
return {info};
}
},
computed: {
funnyName(newState){
// 收集到funnyName对应的依赖是 name
return `${newState.name}_${Date.now()}`
},
otherFunnyName(newState, oldState, fnCtx){
// 获取了funnyName的计算结果和newState.addr作为输入再次计算
// 所以这里收集到otherFunnyName对应的依赖是 name addr
return `${fnCtx.cuVal.funnyName}_${newState.addr}`
}
},
watch:{
// watchKey name和stateKey同名,默认监听name变化
name(newState, oldState){
console.log(`name changed from ${newState.name} to ${oldState.name}`);
},
// 从newState 读取了addr, info两个属性的值,当前watch函数的依赖是 addr, info
// 它们任意一个发生变化时,都会触发此watch函数
addrOrInfoChanged: defWatch((newState, oldState, fnCtx)=>{
const {addr, info} = newState;
if(fnCtx.isFirstCall)return;// 仅为了收集到依赖,不执行逻辑
console.log(`addr is${addr}, info is${JSON.stringify(info)}`);
}, {immediate:true})
}
}
})
function UI(){
console.log('UI with state value');
const {state, sync, dispatch} = useConcent('login');
return (
<div>
name:<input value={state.name} onChange={sync('name')} />
addr:<input value={state.addr} onChange={sync('addr')} />
<br />
info.sex:<input value={state.info.sex} onChange={sync('info.sex')} />
info.grade:<input value={state.info.grade} onChange={sync('info.grade')} />
<br />
<select value={state.info.sex} onChange={(e)=>dispatch('selectSex', e.target.value)}>
<option value="male">male</option>
<option value="female">female</option>
</select>
</div>
);
}
function UI2(){
console.log('UI2 with comptued value');
const {state, moduleComputed, syncBool} = useConcent({module:'login', state:{show:true}});
return (
<div>
{/* 当show为true的时候,当前组件的依赖是funnyName对应的依赖 name */}
{state.show? <span>dep is name: {moduleComputed.funnyName}</span> : 'UI2 no deps now'}
<br/><button onClick={syncBool('show')}>toggle show</button>
</div>
);
}
function UI3(){
console.log('UI3 with comptued value');
const {state, moduleComputed, syncBool} = useConcent({module:'login', state:{show:true}});
return (
<div>
{/* 当show为true的时候,当前组件的依赖是funnyName对应的依赖 name addr */}
{state.show? <span>dep is name,addr: {moduleComputed.otherFunnyName}</span> : 'UI3 no deps now'}
<br/><button onClick={syncBool('show')}>toggle show</button>
</div>
);
}
export default function App() {
return (
<div className="App">
<h3>try click toggle btn and open console to see render log</h3>
<UI />
<UI />
<UI2 />
<UI3 />
</div>
);
}
❤ отметьте меня, если вам нравится концепт ^_^
Если у вас есть какие-либо вопросы о согласии, вы можете отсканировать код, чтобы присоединиться к групповой консультации, и мы постараемся ответить на ваши вопросы и помочь вам узнать больше.