Использование Zustand для управления состоянием в приложении React

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

Что такое Zustand

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

— маленький
Zustand — одна из самых маленьких библиотек управления состояниями, размер пакета составляет всего 1,16 кб.

— быстрый
Zustand работает быстро, благодаря своему небольшому размеру и малому количеству кода, что позволяет нам иметь мгновенное обновление пользовательского интерфейса при изменении состояния.

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

Хватит разговоров, давайте испачкаем руки, создав простое приложение todo и используя Zustand для управления состоянием, чтобы вы увидели, как все работает.

Начало работы с React и Zustand

Установка Zustand в наше приложение react так же проста, как и установка любого другого npm-зависимого приложения react. Чтобы начать работу, выполните следующую команду

npx create-react-app todo-app
cd todo-app
npm install zustand
Войти в полноэкранный режим Выйти из полноэкранного режима

Или, если у вас уже есть существующее приложение react, просто выполните команду

npm install zustand
yarn add zustand
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь, когда наша библиотека управления состояниями установлена в нашем приложении react, мы можем начать работать с ней. Мой файл App.js очень прост

function App() {

  const inputRef = useRef();

  return (
    <div>
      <div style={{ justifyContent: 'center', display: 'flex', padding: '15px' }}>
        <input type={'text'} placeholder='enter user name' style={{ padding: '10px,15px' }} ref={inputRef} />
        <button >Add</button>
        <button >fetch</button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', marginTop: '20px', borderTop: '1px solid black' }}>
        <ul style={{ textAlign: 'center', }}>
        </ul>
      </div>
    </div>
  );
}

export default App;
Войти в полноэкранный режим Выход из полноэкранного режима

Очень простой пользовательский интерфейс для нашего приложения todo. Поле ввода, 2 кнопки и неупорядоченный список для отображения элементов todo.

Создание и доступ к нашему магазину

  • создание магазина

В нашей папке src мы создаем файл store.js, который будет содержать наше глобальное состояние. Наш файл store.js выглядит следующим образом

import create from 'zustand';

let store = () => ({
    users: [{ id: 1, name: 'Teyim Asobo' }, { id: 2, name: 'Fru Brian' }],
})

export const useStore = create(store);
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь мы создали простой магазин, используя функцию create, импортированную из Zustand. Наш магазин представляет собой хук (useStore), и к нему можно получить доступ в любом месте нашего приложения с помощью простого импорта. В настоящее время наш магазин содержит массив пользователей, с 2 пользователями в начальном состоянии.

Теперь, когда наш магазин создан, давайте попробуем получить доступ к нашему состоянию из нашего компонента App.js.

  • Доступ к хранилищу

Мы можем привязать наши компоненты к нашему состоянию, вызывая наши хуки из любой точки нашего приложения.


import { useStore } from './store'
import { useRef } from 'react';

function App() {
  const inputRef = useRef();
  const users = useStore(state => state.users)
  return (
    <div>
      <div style={{ justifyContent: 'center', display: 'flex', padding: '15px' }}>
        <input type={'text'} placeholder='enter user name' style={{ padding: '10px,15px' }} ref={inputRef} />
        <button >Add</button>
        <button >fetch</button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', marginTop: '20px', borderTop: '1px solid black' }}>
        <ul style={{ textAlign: 'center', }}>
          {users?.map(user => (
            <li key={user.id}>
              {user.name}
              <button style={{ marginLeft: '15px' }}>delete</button>
            </li>
          ))}

        </ul>
      </div>
    </div>
  );
}

export default App;
Вход в полноэкранный режим Выйти из полноэкранного режима

Мы импортировали хук useStore из нашего магазина, использовали его для хранения состояния пользователей в переменной users, которую затем выводим в виде списка пользователей.

Обновление состояния

Чтобы иметь возможность обновлять состояние, мы создадим 2 новых свойства в нашем магазине, addUser и deleteUser, которые мы будем использовать для неизменного обновления состояния. Теперь файл store.js выглядит следующим образом

import create from 'zustand';

let store = (set) => ({
    users: [{ id: 1, name: 'Teyim Asobo' }, { id: 2, name: 'Fru Brian' }],
    addUser: (user) => set((state) => ({ users: [...state.users, user] })),
    deleteUser: (userId) => set((state) => ({ users: state.users.filter(user => user.id !== userId) }))
})

export const useStore = create(store);
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, вернувшись в наш компонент App.js, мы сохраним свойства addUser и deleteUser в переменных.

import shallow from 'zustand/shallow'
import { useStore } from './store'

const { addUser, deleteUser, users} = useStore(
    (state) => ({ users: state.users, addUser: state.addUser, deleteUser: state.deleteUser}),
    shallow
  )
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь мы создаем один объект с несколькими пиками состояний для более чистого и красивого кода, а не используем атомарные пики состояний. Shallow — это функция, которую мы импортируем из Zustand. По умолчанию, при использовании атомарных пикировок состояния, Zustand обнаруживает изменения со строгим равенством (old === new). При построении одного объекта с несколькими пикировками, это неэффективно, потому что объект всегда будет воссоздаваться заново и, следовательно (oldObject !== newObject), не вызовет обновления пользовательского интерфейса. Функция Shallow указывает Zustand сравнить значения состояний, перейдя в сам объект и сравнив его ключи, если хоть один из них отличается, то это вызовет обновление.
Теперь наш файл App.js выглядит следующим образом

function App() {
  const { addUser, deleteUser, users } = useStore(
    (state) => ({ users: state.users, addUser: state.addUser, deleteUser: state.deleteUser}),
    shallow
  )
  const inputRef = useRef();

  const addUserHandler = () => {
    addUser({ id: users.length + 1, name: inputRef.current.value })
    inputRef.current.value = ''

  }

  return (
    <div>
      <div style={{ justifyContent: 'center', display: 'flex', padding: '15px' }}>
        <input type={'text'} placeholder='enter user name' style={{ padding: '10px,15px' }} ref={inputRef} />
        <button onClick={addUserHandler}>Add</button>
        <button>fetch</button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', marginTop: '20px', borderTop: '1px solid black' }}>
        <ul style={{ textAlign: 'center', }}>
          {users?.map(user => (
            <li key={user.id}>
              {user.name}
              <button onClick={() => { deleteUser(user.id) }} style={{ marginLeft: '15px' }}>delete</button>
            </li>
          ))}

        </ul>
      </div>
    </div>
  );
}

export default App;

Вход в полноэкранный режим Выйти из полноэкранного режима

addUser и deleteUser можно использовать как обычные функции для добавления и удаления пользователей из состояния.

Обработка асинхронных действий

Zustand делает хранение асинхронных данных в состоянии очень простым. Чтобы продемонстрировать это, мы будем использовать mockapi.io для создания некоторых фиктивных данных, к которым мы можем получить доступ через API, предоставляемый mockapi.io.

let store = (set) => ({
    users: [{ id: 1, name: 'Teyim Asobo' }, { id: 2, name: 'Fru Brian' }],
    addUser: (user) => set((state) => ({ users: [...state.users, user] })),
    deleteUser: (userId) => set((state) => ({ users: state.users.filter(user => user.id !== userId) })),
    fetchUser: async (endpoint) => {
        const reponse = await fetch(endpoint)
        const userData = await reponse.json()
        set({ users: userData })
    }
})
Вход в полноэкранный режим Выход из полноэкранного режима

Мы получаем конечную точку api через свойство fetchUser, получаем данные из конечной точки, конвертируем их в JSON и затем используем функцию set для установки этих данных в наше состояние.
Затем мы можем вызвать и использовать fetchUser в нашем компоненте App.js.

function App() {
  const { addUser, deleteUser, users, fetchUser } = useStore(
    (state) => ({ users: state.users, addUser: state.addUser, deleteUser: state.deleteUser, fetchUser: state.fetchUser }),
    shallow
  )
  const inputRef = useRef();

  const addUserHandler = () => {
    addUser({ id: users.length + 1, name: inputRef.current.value })
    inputRef.current.value = ''

  }

  return (
    <div>
      <div style={{ justifyContent: 'center', display: 'flex', padding: '15px' }}>
        <input type={'text'} placeholder='enter user name' style={{ padding: '10px,15px' }} ref={inputRef} />
        <button onClick={addUserHandler}>Add</button>
        <button onClick={() => fetchUser('https://62fa5e3affd7197707eb05e4.mockapi.io/users')}>fetch</button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', marginTop: '20px', borderTop: '1px solid black' }}>
        <ul style={{ textAlign: 'center', }}>
          {users?.map(user => (
            <li key={user.id}>
              {user.name}
              <button onClick={() => { deleteUser(user.id) }} style={{ marginLeft: '15px' }}>delete</button>
            </li>
          ))}

        </ul>
      </div>
    </div>
  );
}

export default App;
Вход в полноэкранный режим Выход из полноэкранного режима

Конечная точка API дает нам список из 50 пользователей, который мы затем сохраняем в нашем состоянии. Выходные данные выглядят следующим образом

Сохранение данных и использование Devtools

Zustand предоставляет простые в использовании промежуточные модули, которые мы можем использовать для сохранения данных и отслеживания состояния с помощью redux devtools. По умолчанию Zustand сохраняет данные с помощью localstorage, поэтому состояние не теряется после обновления страницы. Частым примером может быть работа с состоянием Auth или простой функцией корзины в приложении электронной коммерции, когда пользователь все еще видит список элементов корзины даже после повторного посещения сайта. Чтобы сохранить состояние нашего приложения, мы импортируем промежуточное ПО persist из Zustand.

import { persist} from 'zustand/middleware'

let store = (set) => ({
    users: [],
    addUser: (user) => set((state) => ({ users: [...state.users, user] })),
    deleteUser: (userId) => set((state) => ({ users: state.users.filter(user => user.id !== userId) })),
    fetchUser: async (endpoint) => {
        const reponse = await fetch(endpoint)
        const userData = await reponse.json()
        set({ users: userData })
    }
})

store = persist(store, { name: 'user' })
export const useStore = create(store);
Вход в полноэкранный режим Выйти из полноэкранного режима

Когда мы теперь нажимаем на кнопку fetch, чтобы получить список пользователей из конечной точки API, который затем сохраняется в state, наше состояние сохраняется в локальном хранилище с ключом users.

Использование Devtools

Zustand предоставляет промежуточное ПО под названием devtools, которое мы можем импортировать и использовать для отслеживания изменений состояния с помощью расширения для браузера Redux Dev tools. Вы можете быть знакомы с этим, если уже работали с Redux.

store = persist(store, { name: 'users' })
store = devtools(store);
export const useStore = create(store);
Вход в полноэкранный режим Выход из полноэкранного режима

Выше мы сохранили наш магазин в локальном хранилище и передали его в промежуточное ПО Devtools. Теперь мы можем контролировать и отслеживать наше состояние в браузере.

Заключение

Zustand — это библиотека управления состоянием, которая помогает разработчикам управлять состоянием приложения. Ее сильными сторонами являются небольшой размер и простота настройки. По сравнению с другими библиотеками, такими как Redux, Zustand помогает разработчикам управлять сложным состоянием приложения с помощью крючков. После прочтения этого руководства вы должны уметь :

  • Настроить Zustand в вашем React-приложении
  • Создать магазин
  • Получать доступ и изменять состояние в хранилище
  • Хранить данные в состоянии через Async запрос
  • Сохранять состояние в локальном хранилище и использовать инструменты разработчиков Redux для отслеживания и мониторинга состояния.

Оцените статью
devanswers.ru
Добавить комментарий