Полный код: Выпуск5
Недавно я работал над личными проектами с React. Поскольку это не крупные проекты, я решаю избавиться от любой библиотеки управления государством.
Для уровня компонентов это просто, мы можем просто использовать крючки, чтобы решить это, например, usestate, пользователь. Но как управлять глобальным государством в приложении React? К счастью, у нас есть контекст. Таким образом, мы можем просто управлять состоянием в компоненте верхнего уровня с пользовательским средством и использовать контекст для передачи этих данных в детские компоненты.
export interface IProfile {
name: string;
age: number;
country: string;
}
export interface IUser {
id: number;
profile: IProfile
}
export interface IStore {
user: IUser;
}
export const initialStore: IStore = {
user: {
id: 0,
profile: {
name: 'initial user name',
age: 0,
country: 'anywhere'
}
},
}
export type Action = (store: IStore) => IStore; // explain later
export interface IStoreContext {
store: IStore,
dispatch: (action: Action) => void;
}
export const initialContext: IStoreContext = {
store: initialStore,
dispatch: () => {
throw new Error('this initial function should never be called')
}
}
export const StoreContext = React.createContext(initialContext);
// explain later
export function reducer(store: IStore, setter: Action) {
return setter(store);
}
import React from 'React';
import { reducer, initialStore, StoreContext } from './Context/StoreContext';
export function App() {
{({ store, dispatch }) => {
return (
{JSON.stringify(store)}
)
}}
}
Пока выглядит хорошо, мы решаем глобальное управление государством с контекстом и крючками. Но есть несколько проблем, меня очень беспокоит. Обычно, когда мы используем Reducer, мы намерены определить много действий и обновление Store, используя большой оператор Switch.
export interface IUpdateUserName {
kind: 'updateUserName'
payload: {
username: string
}
}
type Action = UpdateUserName
export function reducer(store: IStore, action: Action) {
switch(action.kind) {
case 'updateUserName':
return {
...store,
user: {
...store.user,
profile: {
...store.user.profile,
username: action.payload.username
}
}
};
break;
}
}
// Then we can dispatch action in component like this
dispatch({
action: 'updateUserName',
payload: {
username: 'new user name'
}
})
Рассмотрим приведенный выше код, на самом деле не очень приятно обновлять вложенную собственность в штате, хотя оператор спреда спас нас много работы, и проверка типов может убедиться, что мы не обновляем неправильное поле, но можем ли мы сделать его лучше?
Тогда я действительно почему бы не использовать объектив и просто отправить сеттер? Вот почему сначала тип действия определяется как
export type Action = (store: IStore) => IStore
Если вы не знакомы с объективом, вы можете считать его комбинацией функции Getter и Setter. Getter используется для чтения значения, а Setter используется для обновления значения. Вот простая версия объектива
export interface ILens { get: (a: A) => B; set: (b: B) => (a: A) => A; } // construct a Lens from property name // get will be a function to read property object object // set will be a function to set value of object export function lens(prop: P): ILens { return { get: (a: A) => { return a[prop]; }, set: (propValue: A[P]) => { return (a: A) => { return { ...a, [prop]: propValue, } } } } } // compose can combine a fuction to form another Lens // it's useful when we want to read/write nested value export const compose = (lensAB: ILens) => { return (lensBC: ILens): ILens => { return { get: (a: A) => { return lensBC.get(lensAB.get(a)) }, set: (c: C) => { return (a: A) => { const b = lensAB.get(a); const updatedB = lensBC.set(c)(b) return lensAB.set(updatedB)(a) } } } } }
Далее мы можем определить несколько объектива для собственности ISTORE и посмотреть, как отправить объектив, чтобы обновить имя пользователя
export const storeUserLens = lens('user'); export const userProfileLens = lens ('profile') export const profileNameLens = lens ('name'); export const storeUserProfileLens = compose (storeUserLens)(userProfileLens) export const storeUserProfileNameLens = compose (storeUserProfileLens)(profileNameLens) // In component, we can use getter to retrive nested value and dispatch a setter to update user name {({ store, dispatch }) => { return ( ) }}{storeUserProfileNameLens.get(store)}
Обратите внимание, что это определение объектива не очень хорошо сформировано, вы хотите использовать объектив в своем проекте, вы можете попробовать монокл-т
Оригинал: “https://dev.to/jacobchang/combine-lens-context-hooks-to-manage-global-state-in-react-1eg3”