React: Zustand State Manager

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!




Привет, друзья!


На днях, бороздя просторы Сети в поисках вдохновения, наткнулся на Zustand, инструмент для управления состоянием React-приложений, наиболее полно (среди более чем многочисленных альтернатив) отвечающий моим представлениям о, если не идеальном, то адекватном современному React state manager (см., например, эту статью).


Рассказу о нем и будет посвящена данная статья. Сначала немного теории, затем немного практики — по традиции, "запилим" простую "тудушку".


Песочница:

Репозиторий.


Если вам это интересно, прошу под кат.


Теория


Установка


yarn add zustand
# or
npm i zustand

Создание хранилища


Хранилище — это хук. В нем можно хранить что угодно: примитивы, объекты, функции. Функция set объединяет (merge) состояние.


import create from 'zustand'

const useStore = create((set) => ({
 count: 0,
 increment: () => set((state) => ({ count: state.count + 1 })),
 decrement: () => set((state) => ({ count: state.count - 1 })),
 reset: () => set({ count: 0 })
}))

export default useStore

Использование хранилища


Хук можно использовать в любом месте приложения (без провайдера!). Компонент будет повторно рендериться (только) при изменении выбранного состояния.


Использование всего хранилища


export default function Counter() {
 const { count, increment, decrement, reset } = useStore()

 return (
   <main>
     <h2>{count}</h2>
     <div className='btn-box'>
       <button onClick={decrement} className='btn decrement'>
         -
       </button>
       <button onClick={increment} className='btn increment'>
         +
       </button>
       <button onClick={reset} className='btn reset'>
         0
       </button>
     </div>
   </main>
 )
}

В данном случае компонент Counter будет повторно рендериться при любом изменении состояния.


Использование частей состояния (state slices в терминологии Redux)


// хук для "регистрации" повторного рендеринга
function useLogAfterFirstRender(componentName) {
 const firstRender = useRef(true)

 useEffect(() => {
   firstRender.current = false
 }, [])

 if (!firstRender.current) {
   console.log(`${componentName} render`)
 }
}

function Count() {
 const count = useStore(({ count }) => count)

 useLogAfterFirstRender('Count')

 return <h2>{count}</h2>
}

function DecrementBtn() {
 const decrement = useStore(({ decrement }) => decrement)

 useLogAfterFirstRender('Decrement')

 return (
   <button onClick={decrement} className='btn decrement'>
     -
   </button>
 )
}

function IncrementBtn() {
 const increment = useStore(({ increment }) => increment)

 useLogAfterFirstRender('Increment')

 return (
   <button onClick={increment} className='btn increment'>
     +
   </button>
 )
}

function ResetBtn() {
 const reset = useStore(({ reset }) => reset)

 useLogAfterFirstRender('Reset')

 return (
   <button onClick={reset} className='btn reset'>
     0
   </button>
 )
}

const Counter = () => (
 <main>
   <Count />
   <div className='btn-box'>
     <DecrementBtn />
     <IncrementBtn />
     <ResetBtn />
   </div>
 </main>
)

export default Counter

В данном случае будет повторно рендериться только компонент Count и только при изменении значения count.


Рецепты


Если мы перепишем приведенный выше пример следующим образом:


function Count() {
 const count = useStore(({ count }) => count)

 useLogAfterFirstRender('Count')

 return <h2>{count}</h2>
}

function Controls() {
 const { decrement, increment, reset } = useStore(
   ({ decrement, increment, reset }) => ({ decrement, increment, reset })
 )

 useLogAfterFirstRender('Controls')

 return (
   <div className='btn-box'>
     <button onClick={decrement} className='btn decrement'>
       -
     </button>
     <button onClick={increment} className='btn increment'>
       +
     </button>
     <button onClick={reset} className='btn reset'>
       0
     </button>
   </div>
 )
}

const Counter = () => (
 <main>
   <Count />
   <Controls />
 </main>
)

export default Counter

То компонент Controls будет рендериться при любом изменении состояния (потому что объекты сравниваются по ссылке, а не по значению).


Для решения этой проблемы предназначена функция shallow, поверхностно сравнивающая объекты для определения их идентичности и, как следствие, необходимости в повторном рендеринге компонента.


import shallow from 'zustand/shallow'

function Controls() {
 const { decrement, increment, reset } = useStore(
   ({ decrement, increment, reset }) => ({ decrement, increment, reset }),
   /* 						
Источник: https://habr.com/ru/company/timeweb/blog/646339/


Интересные статьи

Интересные статьи

Helm — один из самых популярных пакетных менеджеров для Kubernetes, так что познакомиться с ним поближе стоит всем, кто сталкивается с задачами деплоя приложений. Эта статья завершает мое краткое, но ...
Решения для больших компаний обычно должны выдерживать высокие нагрузки. Когда в штате много десятков тысяч человек, и значительная доля из них ежедневно пользуются ...
State & Transition Diagramm (сокращенно S&T) — схема состояний и переходов. Техника для визуализации ТЗ. Она наглядно показывает, как некий объект переходит из од...
Часто от программистов PHP можно услышать: «О нет! Только не „Битрикс“!». Многие специалисты не хотят связываться фреймворком, считают его некрасивым и неудобным. Однако вакансий ...
Тема статьи навеяна результатами наблюдений за методикой создания шаблонов различными разработчиками, чьи проекты попадали мне на поддержку. Порой разобраться в, казалось бы, такой простой сущности ка...