Observable – удобный state-manager

от автора

Я вас понимаю. Да – еще один. Но давайте посмотрим, вдруг правда?

Давайте определимся с тем, что такое удобно. Конечно, у нас разные представления об удобстве, поэтому я опишу свои с примерами из api react:

  • Отсутствие boilerplate.

// неудобно для меня const [state, setState] = useState({ count: 0 })  // было бы удобнее const state = useState({ count: 0 })
  • Отсутствие snakepit. Термин придумал сам. Под ним я понимаю необходимость совершать и/или оборачивать операции изменения/присвоения в дополнительный код:

// неудобно для меня const [state, setState] = useState({ count: 0 }) const setCount = (value) => setState(prev => ({ count: prev.count + value }))  // было бы удобнее const state = useState({ count: 0 }) const setCount = (value) => state.count = value
  • Ограничения. Например – нельзя вызвать хук внутри условия. Мне неудобно.

Делают ли эти примеры библиотеку react плохой? Разумеется нет, но без этих ограничений – было бы удобнее.

С этой точки зрения, Observable удобен тем, что не накладывает ограничений. Никаких.

Базовый пример

Извините. Дальше будут нормальные примеры, но без каунтера нельзя. Надо соблюдать традиции.

import { Observable, observer } from 'kr-observable'  class CounterState extends Observable {   count = 0    increase() {      ++this.count   }    decrease() {     --this.count   } }  const state = new CounterState()

Это наш стейт. Теперь давайте его используем

// можно так const Counter = observer(() => {   return (     <div>       <button onClick={state.decrease}>         Decrease       </button>       <div>Count: {state.count}</div>         <button onClick={state.increase}>         Increase       </button>     </div>   ) })
// или так const Counter = observer(() => {   return (     <div>       <button onClick={() => --state.count}>         Decrease       </button>       <div>Count: {state.count}</div>         <button onClick={() => ++state.count}>         Increase       </button>     </div>   ) })
// или вот так const increase = () => state.increase() const decrease = () => state.decrease()  const Counter = observer(() => {   return (     <div>       <button onClick={increase}>         Decrease       </button>       <div>Count: {state.count}</div>         <button onClick={decrease}>         Increase       </button>     </div>   ) })

Иными словами работу с Observable можно описать так: Напишите рабочий код на JavaScript. Если хотите связать его с React:

  1. Оберните компоненты в hoc observer

  2. Добавьте нужным классам extends Observable и/или примените к объектам декоратор makeObservable

Надеюсь я вас убедил, что использовать Observable удобно 😉

Дополнительные преимущества

  • Малый размер – 2.7 kb (Gzipped, non minified);

  • Производительная;

  • Framework-agnostic. Из коробки идет с hoc-ом для React, как самой популярной библиотеки, но может работать с любой другой, или без – vanilla, node.js.

Я обещал пример сложнее каунтера. Пожалуйста:

class State extends Observable {   results = []   text = ''   loading = false      // All methods are automatically bounded,    // so you can safely use them as listeners   setText(event: Event) {     this.text = event.target.value   }      async search() {     try {       this.loading = true       const response = await fetch(`/api/search?=${this.text}`)       this.results = await response.json()     } catch(e) {       console.warn(e)     } finally {       this.loading = false     }   }      reset() {     this.results = []   } }  const state = new State()  const Results = observer(function results() {   // Will re-render only if the results change   return (     <div>       {state.results.map(result => <div key={result}>{result}</div>)}     </div>   ) })  const Component = observer(function component() {   // Will re-render only if the text or loading change   return (     <div>       <input          placeholder="Text..."          onChange={state.setText}         disabled={state.loading}         value={state.text}       />       <button          onClick={state.search}         disabled={state.loading}       >         Submit       </button>              <button onClick={state.reset}>          Reset       </button>       <Results />     </div>   ) })

Разумеется, не имеет значения каким образом state оказался внутри Component . При передачи через props поведение не изменится.

Пример с реактивностью. Тут используется autorun , переданная в autorun функция будет вызываться каждый раз, когда в корзину попадает новый товар.

import { Observable, autorun } from 'kr-observable'  class Cart extends Observable {   products = [] }  const cart = new Cart()  let interval  autorun(() => {   const total = catrt.products.reduce((sum, product) => sum + product.price, 0)   if (total > 5000) {     clearInterval(interval)     console.log('Amount is more than 5000')   } })  interval = setInterval(() => {   cart.products.push({ price: 1000 }) }, 1000) // "Amount is more than 5000" (after 5s)

Демо на CodeSandbox

Полная документация на Github или в Npm.

Спасибо что дочитали.


ссылка на оригинал статьи https://habr.com/ru/articles/860820/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *