Как стать автором
Обновить

Мой стейт менеджер для React, Preact, Inferno

Время на прочтение 5 мин
Количество просмотров 4.5K

Люблю изобретать велосипеды и прочие нужные предметы заново. Получается не всегда хорошо, но процесс интересный. Предлагаю вашему вниманию библиотеку управления стейтом для React, Preact (вес всего 4.8Кб). Библиотека еще находится разработке, но уже можно попробовать.

Начнем с примера всеми любимого TODO органайзера. Исходный код на гитхабе. Для начала создадим основной компонент main.js.

// main.js
import React, { createElement, Component, createContext } from 'react';
import ReactDOM from 'react-dom';
import {Connect, Provider} from './store'
import Input from './InputComp'
import TodoList from './TodoList'
import LoadingComp from './LoadingComp'

const Main = () => (
  <Provider>
    <h1>Todo:</h1>
    <LoadingComp>
      <TodoList/>
    </LoadingComp>
    <hr/>
    <Input/>
  </Provider>
)

ReactDOM.render(<Main />, document.getElementById("app"));

Далее стор. Стор нам необходим для инициализации библиотеки, а также тут мы указываем все необходимые файлы с акшенсами. В нашем примере это actions.js и actionsSetup.js

// store.js

import React, { createElement, Component, createContext } from 'react';
import createStoreFactory from 'redoor';

// Экспортируем все функции из actions.js и actionsSetup.js
import * as actions from './actions'
import * as actionsSetup from './actionsSetup'

// здесь мы указываем необходимые функции библиотеки React
const createStore = createStoreFactory({
  Component, 
  createContext, 
  createElement
});

// создаем стор в качестве параметра необходимо указать массив объектов
// всех используемых акшен функций
const { Provider, Connect } = createStore([
  actions,
  actionsSetup
]);

export { Provider, Connect };

Файл с нашими акшенсами и стейтом проекта

// actions.js

// каждый локальный стейт может содержать свой набор переменных 
// redoor автоматически добавит их глобальный стор
// initState зарезервированная переменая она может быть как объект,
// так и функция, которая возвращает объект со стейтом
export const initState = {
    todos:[],
    value:'',
}

// добавляем в массив новую задачу
// переменная state - содержит глобальный стейт
// переменная args - зависит от передаваемых значений из компонента
// возвращает функция новые переменные стейта
export const a_enter = ({state,args}) => {
  let {value,todos} = state;
  todos.push({
    id:(Math.random()+"").substr(2),
    value:value,
    done:false
  });
  return {
    value:'',
    todos
  }
}

// помечаем элемент как сделанное
export const a_done = ({state,args}) => {
  let {todos} = state;
  let id = args.id;
  todos = todos.map(it=>(it.id === id ? (it.done = !it.done, it) : it))
  return {
    todos
  }
}

// удаляем элемент из списка
export const a_delete = ({state,args}) => {
  let {todos} = state;
  let id = args.id;
  todos = todos.filter(it=>it.id !== id)
  return {
    todos
  }
}

Теперь компоненты отображения

// InputComp.js
import React from 'react';
import {Connect} from './store'

// redoor добавляет в пропсы функцию cxRun и все переменные
// глобально стора
const Input = ({cxRun, value})=><label className="input">
  Todo:
  
  // здесь мы можем поменять стор прямо из компонента
  <input onChange={e=>cxRun({value:e.target.value})} 
					value={value} 
					type="text" 
  />
  
  // по нажатию вызываем акшен a_enter из нашено actions.js
  <button onClick={e=>cxRun('a_enter')} disabled={!value.length}>
		ok
	</button>
</label>

// соеденяем с redoor наш компонент и экспортируем 
export default Connect(Input);

cxRun может работать в двух режимах. Первый непосредственно менять содержимое стора, как в случае ввода строки или вызова акшенса из файла actions.js.

И последний компонент выводящий сам список дел.

// TodoList.js
import React from 'react';
import {Connect} from './store'

const Item = ({cxRun, it, v})=><div className="item">
  // вызываем акшен a_done, где в качестве параметра указываем 
  // элемент массива в ашенсе эта переменная будет называться args
  <div className="item_txt" onClick={e=>cxRun('a_done',it)}>
    {v+1}) {it.done ? <s>{it.value}</s> : <b>{it.value}</b>}
  </div>
  <div className="item_del" onClick={e=>cxRun('a_delete',it)}>
    &times;
  </div>
</div>

const TodoList = ({cxRun, todos})=><div className="todos">
  {
    todos.map((it,v)=><Item key={v} cxRun={cxRun} it={it} v={v}/>)
  }
</div>

export default Connect(TodoList);

Теперь по порядку. В нашем проекте в глобальном сторе всего две переменные value и todos. Инициализацией их занимается initState в файле actions.js. initState может быть объектом, так и функцией которая должна вернуть объект со стейтом. Тут важно понимать, что все стейты в акшенс файле помещаются в единый объект и каждый акшенс имеет доступ к любым переменным стейта.

Акшенсы -- это функции которые должны начинаться с префикса "а_" или "action". Имя функции акшенса будет указываться в качестве первого параметра при вызове cxRun. В качестве входного параметра будет объект с переменными state и args.

state -- это весь глобальный стейт проекта

args -- это второй параметр вызова функции cxRun. В нашем проекте при нажатии удалить мы вызываем cxRun('a_delete', it), где первым аргументом будет имя функции акшенса, а вторым сам элемент именно его мы и получаем в args.

Акшенс должен вернуть новое состояние стейта, которое автоматически перерисует компоненты которые подключены к стору.

Что делать если акшен работает асинхронно? Для этого нам необходимо подключить метод setState к локальным переменным файла actions.js с помощью функции bindStateMethods.

//actions.js
let __setState;
let __getState;

// подключаем методы работы со стейтом
export const bindStateMethods = (getState, setState) => {
  __getState = getState;
  __setState = setState;
};

export const a_setup = async ({state,args}) => {
  __setState({loading:true});
  let data = await loading();
  __setState({
    loading:false,
    todos:data
  })
}

Теперь при вызове акшенса "a_load", до начала загрузки появится значок загрузки, а после загрузки данных обновиться массив данных и значок загрузки будет отключен. Если внутри асинхронной функции необходимо получить глобальный стейт, можно вызвать __getState который вернет текущее состояние стейта.

Debugger

Для дебагинга есть инструмент redoor-devtool. Дебаггер это сервер который слушает данные от redoor библиотеки и передает их на одностраничник по адресу localhost:8333. Таким образом дебагер может находится не только в другом браузере, но и на другой машине. Что бывает удобно особенно при разработке для мобильных.

устанавливаем redoor-devtool:

yarn add redoor-devtool

и в отдельной консоли запускаем сервер дебаг консоли

npx redoor-devtool -o

ключик "-o" откроет хром по адресу http://localhost:8333, где будет дебаггер.

Заключение

От себя могу поделиться, что несколько проектов я уже сделал с помощью этой библиотеки. Довольно удобно было работать с ней в проекте с сокетами. Есть конечно и особенности использования. Например, надо помнить, что все акшенсы "видны" из всех модулей. Это не будет проблемой, если иметь четкую структуру наименования акшенсов. В своих проектах использую такое именование "a_moduleName_actionName".

На этом пока все. Будет интерес - постараюсь написать более подробный обзор.

Теги:
Хабы:
+3
Комментарии 3
Комментарии Комментарии 3

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн