1. Введение
unstatedявляется библиотекой управления потоком данных на основе компонента класса,unstated-nextЭто обновленная версия функционального компонента с оптимизированной поддержкой хуков.
По сравнению с библиотекой, подобной редуксу, дизайн этой библиотеки гениален, а количество строк исходного кода этих двух библиотек очень мало. пространство для воображения больше, а использование интуитивно понятно, поэтому интенсивное чтение на этой неделе будет анализировать эти две библиотеки с точки зрения использования и исходного кода.
2 Обзор
Прежде всего, что такое поток данных? React сам обеспечивает поток данных, т.е.setStateа такжеuseState, Смысл существования Framework DataFlow Framework для устранения перекрестного обмена данными и пакетом бизнеса.
Также существует поговорка о том, что React на первых порах позиционировал себя как UI-фреймворк и не заботился о данных, поэтому экосистеме нужно было предоставлять плагины для потока данных, чтобы компенсировать эту возможность. Но на самом деле то, что дает ReactcreateContextа такжеuseContextЭта проблема была решена, но использовать ее немного проблематично, и неустановленная серия призвана решить эту проблему.
unstated
Unstated решает проблему совместного использования данных компонентов в сценарии Class Component.
По сравнению с выбрасыванием употребления напрямую автор восстанавливает авторский мыслительный процесс: использование родногоcreateContextДля реализации потока данных требуются два компонента пользовательского интерфейса, и реализация является подробной:
const Amount = React.createContext(1);
class Counter extends React.Component {
state = { count: 0 };
increment = amount => {
this.setState({ count: this.state.count + amount });
};
decrement = amount => {
this.setState({ count: this.state.count - amount });
};
render() {
return (
<Amount.Consumer>
{amount => (
<div>
<span>{this.state.count}</span>
<button onClick={() => this.decrement(amount)}>-</button>
<button onClick={() => this.increment(amount)}>+</button>
</div>
)}
</Amount.Consumer>
);
}
}
class AmountAdjuster extends React.Component {
state = { amount: 0 };
handleChange = event => {
this.setState({
amount: parseInt(event.currentTarget.value, 10)
});
};
render() {
return (
<Amount.Provider value={this.state.amount}>
<div>
{this.props.children}
<input
type="number"
value={this.state.amount}
onChange={this.handleChange}
/>
</div>
</Amount.Provider>
);
}
}
render(
<AmountAdjuster>
<Counter />
</AmountAdjuster>
);
И что мы делаем,будуsetStateУдалено из определенного компонента пользовательского интерфейса для формирования сущности объекта данных, которую можно внедрить в любой компонент.
ЭтоunstatedКак использовать:
import React from "react";
import { render } from "react-dom";
import { Provider, Subscribe, Container } from "unstated";
class CounterContainer extends Container {
state = {
count: 0
};
increment() {
this.setState({ count: this.state.count + 1 });
}
decrement() {
this.setState({ count: this.state.count - 1 });
}
}
function Counter() {
return (
<Subscribe to={[CounterContainer]}>
{counter => (
<div>
<button onClick={() => counter.decrement()}>-</button>
<span>{counter.state.count}</span>
<button onClick={() => counter.increment()}>+</button>
</div>
)}
</Subscribe>
);
}
render(
<Provider>
<Counter />
</Provider>,
document.getElementById("root")
);
первый дляProviderПравильное имя:ProviderЭто лучшее решение для хранилища размеров.Когда проект и компоненты используют поток данных, когда требуется объем,ProviderЭто пригодилось. Если проекту нужен только один поток данных Store, поместите его с корневым узлом.Providerэквивалентность.
ВторойCounterContainerСтаньте настоящим классом обработки данных, отвечающим только за хранение и обработку данных, через<Subscribe to={[CounterContainer]}>Метод RenderProps будетcounterВнедрено в функцию Render.
По существу неустановленное использование программыsetState, но будетsetStateУдален из пользовательского интерфейса и может быть легко внедрен в любой компонент.
Точно так же его модернизированная версияunstated-nextпо существу используетсяuseState, используя тот факт, что пользовательские хуки можно отделить от пользовательского интерфейса, а такжеuseContextУдобство использования менее 40 строк кода для достижения больше, чемunstatedболее мощные функции.
unstated-next
unstated-nextНазвал финальную версию библиотеки управления данными React в 40 строк кода, посмотрим, как она это сделает!
Или из процесса мышления автор обнаружил, что его README также обеспечивает соответствующий процесс мышления, и взял код в README в качестве примера.
Во-первых, используя функциональный компонент, вы должны использовать поток данных следующим образом:
function CounterDisplay() {
let [count, setCount] = useState(0);
let decrement = () => setCount(count - 1);
let increment = () => setCount(count + 1);
return (
<div>
<button onClick={decrement}>-</button>
<p>You clicked {count} times</p>
<button onClick={increment}>+</button>
</div>
);
}
Если вы хотите отделить данные от пользовательского интерфейса, вы можете сделать это с помощью Custom Hooks без какого-либо фреймворка:
function useCounter() {
let [count, setCount] = useState(0);
let decrement = () => setCount(count - 1);
let increment = () => setCount(count + 1);
return { count, decrement, increment };
}
function CounterDisplay() {
let counter = useCounter();
return (
<div>
<button onClick={counter.decrement}>-</button>
<p>You clicked {counter.count} times</p>
<button onClick={counter.increment}>+</button>
</div>
);
}
Если вы хотите поделиться этими данными с другими компонентами, используйтеuseContextЭто можно сделать без каких-либо рамок:
function useCounter() {
let [count, setCount] = useState(0);
let decrement = () => setCount(count - 1);
let increment = () => setCount(count + 1);
return { count, decrement, increment };
}
let Counter = createContext(null);
function CounterDisplay() {
let counter = useContext(Counter);
return (
<div>
<button onClick={counter.decrement}>-</button>
<p>You clicked {counter.count} times</p>
<button onClick={counter.increment}>+</button>
</div>
);
}
function App() {
let counter = useCounter();
return (
<Counter.Provider value={counter}>
<CounterDisplay />
<CounterDisplay />
</Counter.Provider>
);
}
Но это все еще показывает, чтоuseContextAPI и дляProviderПакет не образует фиксированного шаблона, которыйusestated-nextпроблема, которую нужно решить.
Так что этоunstated-nextКак использовать:
import { createContainer } from "unstated-next";
function useCounter() {
let [count, setCount] = useState(0);
let decrement = () => setCount(count - 1);
let increment = () => setCount(count + 1);
return { count, decrement, increment };
}
let Counter = createContainer(useCounter);
function CounterDisplay() {
let counter = Counter.useContainer();
return (
<div>
<button onClick={counter.decrement}>-</button>
<p>You clicked {counter.count} times</p>
<button onClick={counter.increment}>+</button>
</div>
);
}
function App() {
return (
<Counter.Provider>
<CounterDisplay />
<CounterDisplay />
</Counter.Provider>
);
}
можно увидеть,createContainerЛюбые хуки можно обернуть в объект данных, который имеетProviderа такжеuseContainerдва API, гдеProviderиспользуется для ввода данных в область видимости, аuseContainerВы можете взять экземпляр этого объекта данных в текущей области.
initialStateУстановите данные инициализации, и различные области могут быть вложены друг в друга и им могут быть назначены разные значения инициализации:
function useCounter(initialState = 0) {
let [count, setCount] = useState(initialState);
let decrement = () => setCount(count - 1);
let increment = () => setCount(count + 1);
return { count, decrement, increment };
}
const Counter = createContainer(useCounter);
function CounterDisplay() {
let counter = Counter.useContainer();
return (
<div>
<button onClick={counter.decrement}>-</button>
<span>{counter.count}</span>
<button onClick={counter.increment}>+</button>
</div>
);
}
function App() {
return (
<Counter.Provider>
<CounterDisplay />
<Counter.Provider initialState={2}>
<div>
<div>
<CounterDisplay />
</div>
</div>
</Counter.Provider>
</Counter.Provider>
);
}
Видно, что React Hooks уже очень подходит для управления состоянием, а экология должна максимально использовать его возможности для инкапсуляции паттернов.
Кто-то может спросить, а как насчет извлечения и побочных эффектов?
redux-sagaНикакого другого промежуточного программного обеспечения, этот поток данных является кастрированной версией?
Сначала давайте посмотрим, зачем Redux нужно промежуточное ПО для работы с побочными эффектами. Это потому чтоreducerЭто синхронная чистая функция, и ее возвращаемое значение заключается в том, что в результате операции не может быть асинхронности, и не может быть побочных эффектов, поэтому нам нужен асинхронный вызов.dispatchметод или побочная функция для хранения этой "грязной" логики.
А в Крючки мы можем позвонить в любое времяuseStateкоторый предоставилsetterФункция изменяет значение, которое давно решено естественным образомreducerПроблема невозможности быть асинхронной также решаетсяredux-chunkфункция.
И асинхронная функция тожеuseEffectЭто официальная замена React Hook.Мы видим, что это решение может использовать возможности, официально предоставляемые React, чтобы полностью покрыть возможности промежуточного программного обеспечения Redux и выполнить атаку уменьшения размерности на библиотеку Redux, поэтому решение для потока данных следующего поколения действительно существует с реализацией хуков..
Наконец, по сравнению со стоимостью самого REDUX и его экологической библиотеки (автор не талантлив, это уже давно понятно, когда MiddleWare), а Хуков явно меньше.
Часто люди отказываются от новой технологии не потому, что новая технология не очень хороша, а потому, что она может полностью свести на нет «конкурентное преимущество», приносимое старыми навыками, которые они осваивали в течение многих лет. Возможно, что опытный ткацкий мастер может ткать вручную в 5 раз эффективнее начинающего ученика, но после перехода на ткацкий станок эта разница вскоре сгладится, и старый ткацкий мастер окажется перед кризисом ликвидации, так что поддерживайте это старое ремесло Просто для защиты своих интересов. Надеюсь, что старые ткачи в каждой команде смогут взять на себя инициативу по внедрению ткацкого станка.
Глядя на промежуточное ПО выборки, нам обычно нужно решитьПолучение инкапсуляции бизнес-логикиа такжевыборка состояния инкапсуляции, может быть инкапсулирован через промежуточное ПО redux, через
dispatchрешить.
На самом деле, при мышлении Крюков, используяswr useSWRТо же самое можно решить:
function Profile() {
const { data, error } = useSWR("/api/user");
}
Бизнес-логика извлечения инкапсулирована вfetcher, Это вSWRConfigContext.ProviderОн был введен, и объем также можно контролировать! Полностью используйте возможности контекста, предоставляемые React, вы можете почувствовать последовательность и простоту основного принципа, тем более простые и красивые математические формулы являются правдой.
И состояние выборки было инкапсулировано вuseSWR, с возможностью приостановки вам даже не нужно заботиться о статусе загрузки.
3 Интенсивное чтение
unstated
Давайте разберемсяunstatedЧто делает эта библиотека.
- использовать
ProviderОбъявите сферу деятельности. - поставка
ContainerКак наследуемый класс, наследуйте его класс как хранилище. - поставка
SubscribeStore внедряется как использование RenderProps, инжектируемый экземпляр Store указывается параметромtoОпределяется полученным экземпляром класса.
Для первого пункта,ProviderДля инициализации в среде Class ComponentStateContext, так что вSubscribeиспользуется в:
const StateContext = createReactContext(null);
export function Provider(props) {
return (
<StateContext.Consumer>
{parentMap => {
let childMap = new Map(parentMap);
if (props.inject) {
props.inject.forEach(instance => {
childMap.set(instance.constructor, instance);
});
}
return (
<StateContext.Provider value={childMap}>
{props.children}
</StateContext.Provider>
);
}}
</StateContext.Consumer>
);
}
По второму пункту, дляContainer, который необходимо предоставить в МагазинsetStateAPI, согласно ReactsetStateСтруктура для достижения этого снова.
_listenersобъект и может бытьsubscribeа такжеunsubscribeДополнения и удаления.
_listenersНа самом деле сохраняется текущий связанный компонентonUpdateЖизненный цикл, затем вsetStateАктивировать рендеринг соответствующего компонента.onUpdateжизненный цикл поSubscribeФункция предоставляется, и последний вызовthis.setState, это вSubscribeчасть описания.
Ниже приведеныContainerРеализация кода:
export class Container<State: {}> {
state: State;
_listeners: Array<Listener> = [];
constructor() {
CONTAINER_DEBUG_CALLBACKS.forEach(cb => cb(this));
}
setState(
updater: $Shape<State> | ((prevState: $Shape<State>) => $Shape<State>),
callback?: () => void
): Promise<void> {
return Promise.resolve().then(() => {
let nextState;
if (typeof updater === "function") {
nextState = updater(this.state);
} else {
nextState = updater;
}
if (nextState == null) {
if (callback) callback();
return;
}
this.state = Object.assign({}, this.state, nextState);
let promises = this._listeners.map(listener => listener());
return Promise.all(promises).then(() => {
if (callback) {
return callback();
}
});
});
}
subscribe(fn: Listener) {
this._listeners.push(fn);
}
unsubscribe(fn: Listener) {
this._listeners = this._listeners.filter(f => f !== fn);
}
}
Для третьего пункта,Subscribeизrenderфункция будетthis.props.childrenВыполняется как функция и передает соответствующий экземпляр Store в качестве параметра, что выполняется_createInstancesреализация функции.
_createInstancesиспользоватьinstanceofНайдите соответствующий экземпляр в классе Class и передайтеsubscribeсобственные компонентыonUpdateФункция передается в соответствующий Store_listeners, вызывается при отвязкеunsubscribeОтмените привязку, чтобы предотвратить ненужный рендеринг.
Ниже приведеныSubscribeИсходный код:
export class Subscribe<Containers: ContainersType> extends React.Component<
SubscribeProps<Containers>,
SubscribeState
> {
state = {};
instances: Array<ContainerType> = [];
unmounted = false;
componentWillUnmount() {
this.unmounted = true;
this._unsubscribe();
}
_unsubscribe() {
this.instances.forEach(container => {
container.unsubscribe(this.onUpdate);
});
}
onUpdate: Listener = () => {
return new Promise(resolve => {
if (!this.unmounted) {
this.setState(DUMMY_STATE, resolve);
} else {
resolve();
}
});
};
_createInstances(
map: ContainerMapType | null,
containers: ContainersType
): Array<ContainerType> {
this._unsubscribe();
if (map === null) {
throw new Error(
"You must wrap your <Subscribe> components with a <Provider>"
);
}
let safeMap = map;
let instances = containers.map(ContainerItem => {
let instance;
if (
typeof ContainerItem === "object" &&
ContainerItem instanceof Container
) {
instance = ContainerItem;
} else {
instance = safeMap.get(ContainerItem);
if (!instance) {
instance = new ContainerItem();
safeMap.set(ContainerItem, instance);
}
}
instance.unsubscribe(this.onUpdate);
instance.subscribe(this.onUpdate);
return instance;
});
this.instances = instances;
return instances;
}
render() {
return (
<StateContext.Consumer>
{map =>
this.props.children.apply(
null,
this._createInstances(map, this.props.to)
)
}
</StateContext.Consumer>
);
}
}
Подводить итоги,unstatedСостояние — это внешний пользовательский прослушиватель, реализованный в магазине.setStateСрабатывает при сбореSubscribeРерандер компонента.
unstated-next
unstated-next
- поставка
createContainerИнкапсулирует пользовательские крючки в качестве объекта данных, предоставляяProviderвводят сuseContainerПолучите эти два метода Store.
Как было сказано в предыдущем анализе,unstated-nextМожно сказать, что хуки используются до предела.Считается, что хуки полностью оснащены всеми возможностями управления потоками данных.Нам нужно только упаковать слой спецификаций:
export function createContainer(useHook) {
let Context = React.createContext(null);
function Provider(props) {
let value = useHook(props.initialState);
return <Context.Provider value={value}>{props.children}</Context.Provider>;
}
function useContainer() {
let value = React.useContext(Context);
if (value === null) {
throw new Error("Component must be wrapped with <Container.Provider>");
}
return value;
}
return { Provider, useContainer };
}
видимый,ProviderЭто дляvalueограниченный,Закрепите значение, возвращаемое хуками, непосредственно какvalueПерейти кContext.Providerэта спецификация.
а такжеuseContainerвот такReact.useContext(Context)упаковка.
Другой логики действительно нет.
Единственное, о чем стоит подумать, так это о том, что в кастомных хуках мы используемuseStateуправлять данными илиuseReducerВопрос управления данными является вопросом мнения. Однако мы можем вкладывать и инкапсулировать пользовательские хуки для поддержки некоторых более сложных сценариев данных, таких как:
function useCounter(initialState = 0) {
const [count, setCount] = useState(initialState);
const decrement = () => setCount(count - 1);
const increment = () => setCount(count + 1);
return { count, decrement, increment };
}
function useUser(initialState = {}) {
const [name, setName] = useState(initialState.name);
const [age, setAge] = useState(initialState.age);
const registerUser = userInfo => {
setName(userInfo.name);
setAge(userInfo.age);
};
return { user: { name, age }, registerUser };
}
function useApp(initialState) {
const { count, decrement, increment } = useCounter(initialState.count);
const { user, registerUser } = useUser(initialState.user);
return { count, decrement, increment, user, registerUser };
}
const App = createContainer(useApp);
4 Резюме
заниматьunstated-nextСлоганец: «Никогда не думай о реагирование библиотекам по управлению государством»unstated-nextНикогда больше не рассматривайте другое управление состоянием библиотеки React.
И что интересно,unstated-nextЭто всего лишь модель инкапсуляции хуков.Хуки могут очень хорошо решить проблему управления состоянием.Нам действительно не нужно «перестраивать» инструмент потока данных React.
Адрес обсуждения:Интенсивное чтение «неустановленного и неустановленного-следующего исходного кода» · Выпуск № 218 · dt-fe/weekly
Если вы хотите принять участие в обсуждении, пожалуйста,кликните сюда, с новыми темами каждую неделю, выходящими по выходным или понедельникам. Интерфейс интенсивного чтения — поможет вам отфильтровать надежный контент.
Сфокусируйся наАккаунт WeChat для интенсивного чтения в интерфейсе
Заявление об авторских правах: Бесплатная перепечатка - некоммерческая - не производная - сохранить авторство (Лицензия Creative Commons 3.0)