Я вас понимаю. Да – еще один. Но давайте посмотрим, вдруг правда?
Давайте определимся с тем, что такое удобно. Конечно, у нас разные представления об удобстве, поэтому я опишу свои с примерами из 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:
-
Оберните компоненты в hoc
observer
-
Добавьте нужным классам
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/
Добавить комментарий