Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

A state management library for React combined immutable, mutable and reactive mode

License

NotificationsYou must be signed in to change notification settings

Lucifier129/bistate

Repository files navigation

npm versionBuild StatusDocumentationMaintenanceLicense: MITTwitter: guyingjie129

Create the next immutable state tree by simply modifying the current tree

bistate is a tiny package that allows you to work with the immutable state in a more mutable and reactive way, inspired by vue 3.0 reactivity API and immer.

Benefits

bistate is like immer but more reactive

  • Immutability with normal JavaScript objects and arrays. No new APIs to learn!
  • Strongly typed, no string based paths selectors etc.
  • Structural sharing out of the box
  • Deep updates are a breeze
  • Boilerplate reduction. Less noise, more concise code.
  • Provide react-hooks API
  • Small size
  • Reactive

Environment Requirement

  • ES2015 Proxy
  • ES2015 Symbol

Can I Use Proxy?

How it works

Every immutable state is wrapped by a proxy, has a scapegoat state by the side.

immutable state +scapegoat state =bistate

  • the immutable target is freezed by proxy
  • scapegoat has the same value as the immutable target
  • mutate(() => {the_mutable_world }), when callingmutate(f), it will
    • switch all operations to scapegoat instead of the immutable target when executing
    • switch back to the immutable target after executed
    • create the next bistate viascapegoat andtarget, sharing the unchanged parts
    • we get two immutable states now

Install

npm install --save bistate
yarn add bistate

Usage

Counter

importReactfrom'react'// import react-hooks api from bistate/reactimport{useBistate,useMutate}from'bistate/react'exportdefaultfunctionCounter(){// create state via useBistateletstate=useBistate({count:0})// safely mutate state via useMutateletincre=useMutate(()=>{state.count+=1})letdecre=useMutate(()=>{state.count-=1})return(<div><buttononClick={incre}>+1</button>{state.count}<buttononClick={decre}>-1</button></div>)}

TodoApp

functionTodo({ todo}){letedit=useBistate({value:false})/**   * bistate text is reactive   * we will pass the text down to TodoInput without the need of manually update it in Todo   * */lettext=useBistate({value:''})// create a mutable function via useMutatelethandleEdit=useMutate(()=>{edit.value=!edit.valuetext.value=todo.content})lethandleEdited=useMutate(()=>{edit.value=falseif(text.value===''){// remove the todo from todos via remove functionremove(todo)}else{// mutate todo even it is not a local bistatetodo.content=text.value}})lethandleKeyUp=useMutate(event=>{if(event.key==='Enter'){handleEdited()}})lethandleRemove=useMutate(()=>{remove(todo)})lethandleToggle=useMutate(()=>{todo.completed=!todo.completed})return(<li><buttononClick={handleRemove}>remove</button><buttononClick={handleToggle}>{todo.completed ?'completed' :'active'}</button>{edit.value&&<TodoInputtext={text}onBlur={handleEdited}onKeyUp={handleKeyUp}/>}{!edit.value&&<spanonClick={handleEdit}>{todo.content}</span>}</li>)}functionTodoInput({ text, ...props}){lethandleChange=useMutate(event=>{/**     * we just simply and safely mutate text at one place     * instead of every parent components need to handle `onChange` event     */text.value=event.target.value})return<inputtype="text"{...props}onChange={handleChange}value={text.value}/>}

API

import{createStore,mutate,remove,isBistate,debug,undebug}from'bistate'import{useBistate,useMutate,useBireducer,useComputed,useBinding,view,useAttr,useAttrs}from'bistate/react'

useBistate(array | object, bistate?) -> bistate

receive an array or an object, return bistate.

if the second argument is another bistate which has the same shape with the first argument, return the second argument instead.

letChild=(props:{counter?:{count:number}})=>{// if props.counter is existed, use props.counter, otherwise use local bistate.letstate=useBistate({count:0},props.counter)lethandleClick=useMutate(()=>{state.count+=1})return<divonClick={handleClick}>{state.count}</div>}// use local bistate<Child/>// use parent bistate<Childcounter={state}/>

useMutate((...args) => any_value) -> ((...args) => any_value)

receive a function as argument, return the mutable_function

it's free to mutate any bistates in mutable_function, not matter where they came from(they can belong to the parent component)

useBireducer(reducer, initialState) -> [state, dispatch]

receive a reducer and an initial state, return a pair [state, dispatch]

its' free to mutate any bistates in the reducer funciton

import{useBireducer}from'bistate/react'constTest=()=>{let[state,dispatch]=useBireducer((state,action)=>{if(action.type==='incre'){state.count+=1}if(action.type==='decre'){state.count-=1}},{count:0})lethandleIncre=()=>{dispatch({type:'incre'})}lethandleIncre=()=>{dispatch({type:'decre'})}// render view}

useComputed(obj, deps) -> obj

Create computed state

letstate=useBistate({first:'a',last:'b'})// use getter/setterletcomputed=useComputed({getvalue(){returnstate.first+' '+state.last},setvalue(name){let[first,last]=name.split(' ')state.first=firststate.last=last}},[state.first,state.last])lethandleEvent=useMutate(()=>{console.log(computed.value)// 'a b'// updatecomputed.value='Bill Gates'console.log(state.first)// Billconsole.log(state.last)// Gates})

useBinding(bistate) -> obj

Create binding state

A binding state is an object has only one filed{ value }

letstate=useBistate({text:'some text'})let{ text}=useBinding(state)// don't do this// access field will trigger a react-hooks// you should always use ECMAScript 6 (ES2015) destructuring to get binding stateletbindingState=useBinding(state)if(xxx)xxx=bindingState.xxxlethandleChange=()=>{console.log(text.value)// some textconsole.log(state.text)// some texttext.value='some new text'console.log(text.value)// some new textconsole.log(state.text)// some new text}

It's useful when child component needs binding state, but parent component state is not.

functionInput({ text, ...props}){lethandleChange=useMutate(event=>{/**     * we just simply and safely mutate text at one place     * instead of every parent components need to handle `onChange` event     */text.value=event.target.value})return<inputtype="text"{...props}onChange={handleChange}value={text.value}/>}functionApp(){letstate=useBistate({fieldA:'A',fieldB:'B',fieldC:'C'})let{ fieldA, fieldB, fieldC}=useBinding(state)return<><Inputtext={fieldA}/><Inputtext={fieldB}/><Inputtext={fieldC}/></>}

view(FC) -> FC

create a two-way data binding function-component

constCounter=view(props=>{// Counter will not know the count is local or came from the parentletcount=useAttr('count',{value:0})lethandleClick=useMutate(()=>{count.value+=1})return<buttononClick={handleClick}>{count.value}</button>})// use local bistate<Counter/>// create a two-way data binding connection with parent bistate<Countcount={parentBistate.count}/>

useAttrs(initValue) -> Record<string, bistate>

create a record of bistate, when the value in props[key] is bistate, connect it.

useAttrs must use in view(fc)

constTest=view(()=>{// Counter will not know the count is local or came from the parentletattrs=useAttrs({count:{value:0}})lethandleClick=useMutate(()=>{attrs.count.value+=1})return<buttononClick={handleClick}>{attrs.count.value}</button>})// use local bistate<Counter/>// create a two-way data binding connection with parent bistate<Countcount={parentBistate.count}/>

useAttr(key, initValue) -> bistate

a shortcut ofuseAttrs({ [key]: initValue })[key], it's useful when we want to separate attrs

createStore(initialState) -> { subscribe, getState }

create a store with an initial state

store.subscribe(listener) -> unlisten

subscribe to the store, and return an unlisten function

Every time the state has been mutated, a new state will publish to every listener.

store.getState() -> state

get the current state in the store

letstore=createStore({count:1})letstate=store.getState()letunlisten=store.subscribe(nextState=>{expect(state).toEqual({count:1})expect(nextState).toEqual({count:2})unlisten()})mutate(()=>{state.count+=1})

mutate(f) -> value_returned_by_f

immediately execute the function and return the value

it's free to mutate the bistate in mutate function

remove(bistate) -> void

remove the bistate from its parent

isBistate(input) -> boolean

check if input is a bistate or not

debug(bistate) -> void

enable debug mode, break point when bistate is mutating

undebug(bistate) -> void

disable debug mode

Caveats

  • only supports array and object, other data types are not allowed

  • bistate is unidirectional, any object or array appear only once, no circular references existed

letstate=useBistate([{value:1}])mutate(()=>{state.push(state[0])// nextState[0] is equal to state[0]// nextState[1] is not equal to state[0], it's a new one})
  • can not spread object or array as props, it will lose the reactivity connection in it, should pass the reference
// don't do this<Todo{...todo}/>// do this instead<Todotodo={todo}/>
  • can not edit state or props via react-devtools, the same problem as above

  • useMutate or mutate do not support async function

constTest=()=>{letstate=useBistate({count:0})// don't do thislethandleIncre=useMutate(async()=>{letn=awaitfetchData()state.count+=n})// do this insteadletincre=useMutate(n=>{state.count+=n})lethandleIncre=async()=>{letn=awaitfetchData()incre(n)}return<divonClick={handleIncre}>test</div>}

Author

👤Jade Gu

🤝 Contributing

Contributions, issues and feature requests are welcome!

Feel free to checkissues page.

Show your support

Give a ⭐️ if this project helped you!

📝 License

Copyright © 2019Jade Gu.

This project isMIT licensed.


This README was generated with ❤️ byreadme-md-generator

About

A state management library for React combined immutable, mutable and reactive mode

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp