«React Advanced» может заменить React-Redux только двумя пользовательскими хуками?

внешний интерфейс JavaScript React.js

предисловие

Друг спросил меня раньше, могут ли React Hooks решить проблему управления состоянием проектов React. Этот вопрос заставил меня долго думать, и наконец пришел к выводу:Да, но для реализации требуется два пользовательских хука. Так как именно это достигается? Вот о чем мы сегодня поговорим.

Благодаря этой статье вы можете узнать следующее:

  • Базовое использование useContext, useRef, useMemo, useEffect.
  • Как установить связь между кастомными хуками разных компонентов и поделиться состоянием.
  • Разумно пишите собственные хуки и анализируйте зависимости между хуками.
  • Некоторые подробности в процессе написания кастомных хуков.

Имея вышеуказанные знания, давайте начнем путешествие по чтению~ (Создавать не так просто, я надеюсь, что вы можете сделать автору комплимент перед экраном, чтобы побудить меня продолжать создавать интерфейсный жесткий текст.)

Идея дизайна

Во-первых, давайте посмотрим на конкретные функции двух настраиваемых хуков, которые необходимо реализовать.

  • useCreateStoreИспользуется для создания хранилища состояний, передаваемого через контекст контекста, для каждого пользовательского хука.useConnectМожет получить атрибут состояния в контексте.
  • useConnectИспользуя этот пользовательский компонент хуков, вы можете получить метод отправки, который изменяет состояние, а также вы можете подписаться на состояние.При изменении состояния подписки компонент обновляется.

Как сделать так, чтобы кастомные хуки разных компонентов делились состоянием и реализовывали коммуникацию?

Во-первых, через него можно передавать пользовательские хуки различных компонентов.useContextЧтобы получить общее состояние, а также реализовать управление состоянием и связь между компонентами, необходим центр планирования состояния, чтобы делать эти вещи единообразно, который можно назватьReduxHooksStore, который конкретно делает следующее:

  • Глобально управляйте состоянием, изменениями состояния и уведомляйте соответствующие компоненты об обновлении.
  • собирать и использоватьuseConnectинформация о компонентах. Уничтожение компонента также очищает эту информацию.
  • поддерживать и доставлять обновления, отвечающие заdispatchметод.
  • Некоторые важные API подвергаются контекстному контексту, который передается каждомуuseConnect.

1 использованиеCreateStore дизайн

первыйuseCreateStoreОн находится рядом с корневым компонентом, и глобально нужен только один, целью которого является созданиеStore, и пройтиProviderпередай.

использовать:

const store = useCreateStore( reducer , initState )

параметр:

  • reducer: Глобальный редуктор, чистая функция, переданная в состоянии и действии, возвращает новое состояние.
  • initState: состояние инициализации.

Возвращаемое значение: основная функциональная функция, доступная для магазина.

2 Дизайн магазина

Store — это вышеупомянутый диспетчерский центр, который получает глобальный редьюсер, поддерживает внутреннее состояние, отвечает за уведомление об обновлениях и собирает компоненты, использующие useConnect.

const Store = new ReduxHooksStore(reducer,initState).exportStore()

Параметры: Получите два параметра и прозрачно передайте параметры useCreateStore.

3 дизайн useConnect

Компоненты, которые используют useConnect, получат функцию отправки для обновления состояния. Вы также можете подписаться на состояние через первый параметр. Изменения в состоянии подписки вызовут обновление компонента.

// 订阅 state 中的 number 
const mapStoreToState = (state)=>({ number: state.number  })
const [ state , dispatch ] = useConnect(mapStoreToState)

параметр:

  • mapStoreToState: сопоставьте состояние в Store с состоянием компонента, которое можно использовать для визуализации представления.
  • Если нет первого параметра, укажите толькоdispatchФункция, состояние не подписывается на обновление, вносит изменения.

Возвращаемое значение: возвращаемое значение представляет собой массив.

  • Первый элемент массива: значение отображенного состояния.
  • Второй элемент массива: изменить состояниеdispatchфункция.

4 Схема

7.jpg

Два использованияCreateStore

export const ReduxContext = React.createContext(null)
/* 用于产生 reduxHooks 的 store */
export function useCreateStore(reducer,initState){
   const store = React.useRef(null)
   /* 如果存在——不需要重新实例化 Store */
   if(!store.current){
       store.current  = new ReduxHooksStore(reducer,initState).exportStore()
   }
   return store.current
}

useCreateStoreГлавное:

  • перениматьreducerа такжеinitState, создайте хранилище через ReduxHooksStore, не ожидайте, что все хранилище будет доступно пользователям, нужно только предоставить основные методы, поэтому вызовите под экземпляромexportStoreИзвлеките основной метод.

  • использоватьuseRefСохраните основной метод, переданный вProvider.

Три менеджера состояний - ReduxHooksStore

Затем взгляните на основное состояние ReduxHooksStore.

import { unstable_batchedUpdates } from 'react-dom'
class ReduxHooksStore {
    constructor(reducer,initState){
       this.name = '__ReduxHooksStore__'
       this.id = 0
       this.reducer = reducer
       this.state = initState
       this.mapConnects = {}
    }
    /* 需要对外传递的接口 */
    exportStore=()=>{
        return {
            dispatch:this.dispatch.bind(this),
            subscribe:this.subscribe.bind(this),
            unSubscribe:this.unSubscribe.bind(this),
            getInitState:this.getInitState.bind(this)
        }
    }
    /* 获取初始化 state */
    getInitState=(mapStoreToState)=>{
        return mapStoreToState(this.state)
    }
    /* 更新需要更新的组件 */
    publicRender=()=>{
        unstable_batchedUpdates(()=>{ /* 批量更新 */
            Object.keys(this.mapConnects).forEach(name=>{
                const { update } = this.mapConnects[name]
                update(this.state)
            })
        })
    }
    /* 更新 state  */
    dispatch=(action)=>{
       this.state = this.reducer(this.state,action)
       // 批量更新
       this.publicRender()
    }
    /* 注册每个 connect  */
    subscribe=(connectCurrent)=>{
        const connectName = this.name + (++this.id)
        this.mapConnects[connectName] =  connectCurrent
        return connectName
    }
    /* 解除绑定 */
    unSubscribe=(connectName)=>{
        delete this.mapConnects[connectName]
    }
}

условие

  • reducer: этот редюсер является глобальным редюсером, переданным useCreateStore .
  • state: глобально сохраненное состояние состояния, каждый раз при выполнении редюсера будет получено новое состояние.
  • mapConnects: хранит функцию обновления каждого компонента useConnect. Используется для отправки обновлений из изменений состояния.

метод

Ответственный за инициализацию:

  • getInitState: этот метод используется useConnect пользовательских хуков для получения инициализированного состояния.
  • exportStore: этот метод используется для передачи основных методов, предоставляемых ReduxHooksStore, каждому useConnect .

Ответственный за привязку | отвязку:

  • subscribe: привязать все пользовательские хуки useConnect .
  • unSubscribe: Отвязать каждый крючок.

Ответственный за обновление:

  • dispatch: этот метод предоставляется на уровне бизнес-компонента.Каждый компонент, использующий useConnect, может изменить состояние с помощью метода диспетчеризации.Внутренний принцип заключается в создании нового состояния путем вызова редуктора.

  • publicRender: Когда состояние изменяется, каждый компонент, который использует useConnect, должен быть уведомлен. Этот метод предназначен для уведомления об обновлении. Что касается того, должен ли компонент обновляться или нет, это то, что должно обрабатываться внутри useConnect. Вот еще одна деталь, учитывая, что сценарий срабатывания отправки может быть в асинхронном состоянии, используйте нестабильность_batchedUpdates в React-DOM, чтобы включить принцип пакетного обновления.

Четыре использованияConnect

useConnect — это основная часть всей функции, которая позволяет получать последниеstate, а затем передайте функцию подпискиmapStoreToStateПолучите состояние подписки и определите, изменилось ли состояние подписки. Визуализируйте последнее состояние, если есть изменения.

export function useConnect(mapStoreToState=()=>{}){
    /* 获取 Store 内部的重要函数 */
   const contextValue = React.useContext(ReduxContext)
   const { getInitState , subscribe ,unSubscribe , dispatch } = contextValue
   /* 用于传递给业务组件的 state  */
   const stateValue = React.useRef(getInitState(mapStoreToState))

   /* 渲染函数 */
   const [ , forceUpdate ] = React.useState()
   /* 产生 */
   const connectValue = React.useMemo(()=>{
       const state =  {
           /* 用于比较一次 dispatch 中,新的 state 和 之前的state 是否发生变化  */
           cacheState: stateValue.current,
           /* 更新函数 */
           update:function (newState) {
               /* 获取订阅的 state */
               const selectState = mapStoreToState(newState)
               /* 浅比较 state 是否发生变化,如果发生变化, */
               const isEqual = shallowEqual(state.cacheState,selectState)
               state.cacheState = selectState
               stateValue.current  = selectState
               if(!isEqual){
                   /* 更新 */
                   forceUpdate({})
               }
           }
       }
       return state
   },[ contextValue ]) // 将 contextValue 作为依赖项。

   React.useEffect(()=>{
       /* 组件挂载——注册 connect */
       const name =  subscribe(connectValue)
       return function (){
            /* 组件卸载 —— 解绑 connect */
           unSubscribe(name)
       }
   },[ connectValue ]) /* 将 connectValue 作为 useEffect 的依赖项 */

   return [ stateValue.current , dispatch ]
}

инициализация

  • Используйте useContext, чтобы получить основную функцию, предоставляемую ReduxHooksStore в контексте.
  • Используйте useRef для сохранения последнего полученного состояния.
  • Создайте функцию обновления с помощью useStateforceUpdate, эта функция просто обновляет компонент.

Регистрация|Процесс отмены привязки

  • Регистрация: пройтиuseEffectДавайте зарегистрируем connectValue, сгенерированное текущим useConnect, с помощью ReduxHooksStore Что такое connectValue, мы вскоре обсудим. subscribe используется для регистрации и возвращает имя уникального идентификатора текущего connectValue.

  • Unbind: в функции уничтожения useEffect вы можете отменить привязку текущего connectValue, вызвав unSubscribe и передав имя

Обновляет ли connectValue компонент

  • connectValue : состояние, которое фактически зарегистрировано в ReduxHooksStore, первое использованиеuseMemoДля кэширования connectValue connectValue — это объект, cacheState в нем сохраняет состояние, сгенерированное последним mapStoreToState, а за обновление отвечает функция update.

  • процесс обновления: при срабатыванииdispatchВ ReduxHooksStore будет выполнено обновление каждого connectValue, и обновление вызовет функцию сопоставления.mapStoreToStateчтобы получить содержимое состояния, которое хочет текущий компонент. затем пройтиshallowEqualПоверхностно сравните, изменилось ли старое и новое состояние, и если да, обновите компонент. Завершите весь процесс.

  • мелкое сравнение: это поверхностное сравнение в React Процесс описан в главе 11, поэтому я не буду говорить о нем здесь.

Определить зависимости

  • Во-первых, зависимость кастомных хуков useConnect заключается в том, что изменяется контекст contextValue, а это значит, что изменилось хранилище, поэтому через useMemo снова генерируется новое connectValue.Так что useMemo зависит от contextValue.

  • Если connectValue изменится, вам необходимо освободить исходное отношение привязки и выполнить привязку повторно.useEffect зависит от connectValue.

ограничение

Весь useConnect имеет некоторые ограничения, такие как:

  • MapStoreToState не может передаваться динамически без учета изменчивости mapStoreToState.
  • Поверхностное сравнение, а не глубокое сравнение ссылочных типов данных.

5. Используйте и проверьте эффект

Следующим шагом будет проверка эффекта Я смоделировал сценарий взаимодействия компонентов.

Корневой компонент внедряется в Store

import { ReduxContext , useConnect , useCreateStore } from './hooks/useRedux'
function  Index(){
    const [ isShow , setShow ] =  React.useState(true)
    console.log('index 渲染')
    return <div>
        <CompA />
        <CompB />
        <CompC />
        {isShow &&  <CompD />}
        <button onClick={() => setShow(!isShow)} >点击</button>
    </div>
}

function Root(){
    const store = useCreateStore(function(state,action){
        const { type , payload } =action
        if(type === 'setA' ){
            return {
                ...state,
                mesA:payload
            }
        }else if(type === 'setB'){
            return {
                ...state,
                mesB:payload
            }
        }else if(type === 'clear'){ //清空
            return  { mesA:'',mesB:'' }
        }
        else{
            return state
        }
    },
    { mesA:'111',mesB:'111' })
    return <div>
        <ReduxContext.Provider value={store} >
            <Index/>
        </ReduxContext.Provider>
    </div>
}

Корневой компонент

  • Создайте хранилище с помощью useCreateStore, передайте редьюсер и инициализированное значение.{ mesA:'111',mesB:'111' }
  • Пройти магазин с Провайдером.

Компонент индекса

  • Есть четыре подкомпонента CompA, CompB, CompC, CompD. Где CompD динамически монтируется.

Использование бизнес-компонентов

function CompA(){
    const [ value ,setValue ] = useState('')
    const [state ,dispatch ] = useConnect((state)=> ({ mesB : state.mesB }) )
    return <div className="component_box" >
        <p> 组件A</p>
        <p>组件B对我说 : {state.mesB} </p>
        <input onChange={(e)=>setValue(e.target.value)}
            placeholder="对B组件说"
        />
        <button onClick={()=> dispatch({ type:'setA' ,payload:value })} >确定</button>
    </div>
}

function CompB(){
    const [ value ,setValue ] = useState('')
    const [state ,dispatch ] = useConnect((state)=> ({ mesA : state.mesA }) )
    return <div className="component_box" >
        <p> 组件B</p>
        <p>组件A对我说 : {state.mesA} </p>
        <input onChange={(e)=>setValue(e.target.value)}
            placeholder="对A组件说"
        />
        <button onClick={()=> dispatch({ type:'setB' ,payload:value })} >确定</button>
    </div>
}

function CompC(){
    const [state  ] = useConnect((state)=> ({ mes1 : state.mesA,mes2 : state.mesB }) )
    return <div className="component_box" >
        <p>组件A : {state.mes1} </p>
        <p>组件B : {state.mes2} </p>
    </div>
}

function CompD(){
    const [ ,dispatch  ] = useConnect( )
    console.log('D 组件更新')
    return <div className="component_box" >
        <button onClick={()=> dispatch({ type:'clear' })} > 清空 </button>
    </div>
}

  • Аналоговые компоненты CompA и CompB взаимодействуют в двух направлениях.
  • Компонент CompC получает сообщения CompA и CompB и сопоставляется сmes1 ,mes2характеристики.
  • CompD не имеет ни mapStoreToState, ни состояния подписки, компонент изменения состояния не будет обновляться, просто используйте диспетчеризацию для очистки состояния.

Эффект

8.gif

Шесть резюме

В этой статье реализованы базовые функции React-Redux через два кастомных хука.Можно ли использовать этот паттерн в реальных проектах? Я думаю, что если это небольшой проект, его можно использовать полностью, для больших проектов используйте React Redux или другие зрелые инструменты управления состоянием.

Буклет «Руководство по расширенной практике React» доступен онлайн

Сегодня я всем рекомендую буклет Nuggets.«Расширенное практическое руководство по React», пользовательские хуки в этой статье также являются примером в главе буклета, посвященной настраиваемым хукам. В буклете есть много случаев разработки нестандартных крючков, а главы о дизайне и практике нестандартных крючков постоянно обновляются и поддерживаются, что объединяет усилия автора на протяжении многих лет.Заинтересованные студенты могут узнать о следующем.Ниже приводится введение буклет.

В этом буклете обсуждается руководство по использованию React и введение в принципы по пяти направлениям: основные продвинутые статьи, продвинутые статьи по оптимизации, продвинутые статьи о принципах, продвинутые экологические статьи и продвинутые практические статьи.

1.jpg

  • В базовой расширенной главе мы заново поймем состояние, свойства, ссылку, контекст и другие модули в реакции, а также подробно объясним их основное использование и игровой процесс высокого уровня.
  • В главе о расширенной оптимизации мы расскажем о настройке производительности React и подробной обработке, чтобы React можно было написать более элегантно.
  • В расширенной главе о принципах будут объяснены принципы нескольких основных модулей React, и принципы React будут рассмотрены в разовых интервью.
  • В расширенной экологической главе мы рассмотрим использование ключевой экологии React и проанализируем внутренний механизм работы с принципиальной точки зрения.
  • В главе расширенной практики первые несколько модулей будут соединены последовательно для интенсивной практики.

Что касается того, почему буклет называется Advanced Practice Guide, потому что, объясняя расширенный игровой процесс, он также содержит множество небольших демонстраций для практики. В интервью также есть несколько сессий вопросов и ответов, чтобы читатели выделялись из интервью.

Буклет теперь онлайн, вот 2 кода скидки на скидку 30%, в порядке живой очереди.

  • Буклет Код скидки 30%:cRftnJvJ
  • Буклет Код скидки 30%:5EPxuNV5