Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

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
Appearance settings

Type-safe, composable, boilerplateless reducers

NotificationsYou must be signed in to change notification settings

betafcc/red

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Red

Type-safe, composable, boilerplateless reducers

Install

npm install --save @betafcc/red

By example

importReact,{useReducer}from'react'import{render}from'react-dom'import{red}from'@betafcc/red'constinputApp=red.withState({input:''}).handle({setInput:(state,input:string)=>({ input})})consttodoApp=inputApp.withState({todos:[]asArray<{msg:string;done:boolean}>}).handle({add:(state,msg:string)=>({input:'',todos:[...s.todos,{ msg,done:false}]}),complete:(state,id:number)=>({todos:state.todos.map((e,i)=>i!==id ?e :{ ...e,done:true})}),})exportconstTodoApp=()=>{const[{ input, todos},{ setInput, add, complete}]=todoApp.useHook(useReducer)return<><inputvalue={input}onChange={e=>setInput(e.target.value)}/><buttononClick={_=>add(input)}>add</button>{todos.map((e,id)=><likey={id}style={e.done ?{textDecoration:'line-through'} :{}}>{e.msg}<buttononClick={_=>complete(id)}>done</button></li>)}</>}render(<TodoApp/>,document.getElementById('root'))

The idea

If every action has this shape:

typeActionType<Kextendsstring,PextendsArray<unknown>>={type:Kpayload:P}

We can automatically provide action creators and strong-typed reducer from simple handlers definitions:

constapp=red.withState({input:'',todo:[]asArray<{msg:string,id:number,done:boolean}>}).handle({setInput(state,input:string){return{ input}},addTodo(state,msg:string,id:number){return{todo:[...state.todo,{msg, id,done:false}]}},completeTodo(state,id:number){return{todo:todo.map(t=>t.id!==id ?t :{...t,done:true})}}})const{  intial,// the initial state  reducer,// the reducer made by combining the handlers  actions,// the action creators}=red

The arguments in the handlers define the payload, and their keys define the 'type', the revealed signature is:

import{StateOf,ActionOf}from'@betafcc/red'typeState=StateOf<typeofred>// { input: string, todo: Array<{msg: string, id: number, done: boolean}>}typeAction=ActionOf<typeofred>// { type: 'setInput', payload: [string] } | { type: 'addTodo', payload: [string, number] } | { type: 'completeTodo', payload: [number] }

And you also have action creators that matches the signature:

const{ addTodo, completeTodo}=app.actionsaddTodo('buy milk',0)// { type: 'addTodo', payload: ['buy milk', 0] }completeTodo(0)// { type: 'completeTodo', payload: [number] }

If your prefer to define the actions yourself, you can useActionType helper andwithActions method:

import{ActionType}from'@betafcc/red'typeAction=|ActionType<'setInput',[string]>|ActionType<'addTodo',[string,number]>|ActionType<'completeTodo',[number]>constapp=red.withState({input:'',todo:[]asArray<{msg:string,id:number,done:boolean}>}).withActions<Action>({// annotate heresetInput(state,input){// so arguments will have infered type, no need to annotate herereturn{input}},addTodo(state,msg,id){return{todo:[...state.todo,{msg, id,done:false}]}},completeTodo(state,id){return{todo:todo.map(t=>t.id!==id ?t :{...t,done:true})}}})

Use it

The simplest way to use is to extract the generatedreducer,initial and theactions creators:

const{reducer, initial, actions}=appconstApp=()=>{const[state,dispatch]=React.useReducer(reducer,initial)return<>{state.count}<buttononClick={_=>dispatch(actions.increment())}>+</button></>}

But you can usered.useHook for some extra magic:

constApp=()=>{// the action creators will become dispatchersconst[state,{increment}]=app.useHook(React.useReducer)return<>{state.count}<buttononClick={_=>increment()}>+</button></>}

Merge

You can usered.merge to combine apps together:

constinputApp=red.withState({input:''}).handle({setInput:(s,value:string)=>({input:value})})consttodoApp=red.withState({todos:[]asArray<{id:number,done:boolean,msg:string}>}).handle({add:(s,msg:string,id:number)=>({todos:[...s.todos,{id, msg,done:false}]})})constapp=red.merge(inputApp).merge(todoApp)// same asconstapp=inputApp.merge(todoApp)// same asconstapp=red.withState({input:''}).handle({setInput:(s,value:string)=>({input:value})}).withState({todos:[]asArray<{id:number,done:boolean,msg:string}>}).handle({add:(s,msg:string,id:number)=>({todos:[...s.todos,{id, msg,done:false}]})})

Combine

Or you can combine them by namespacing withred.combine, similar to redux'scombineReducers:

constinputApp=red.withState({input:''}).handle({setInput:(s,input:string)=>({input})})constcounterApp=red.withState({count:0}).handle({increment:s=>({count:s.count+1})})constapp=red.combine({ui:inputApp,counter:counterApp})// equivalent to:constapp=red.withState({ui:{input:''},counter:{count:0}}).handle({// note that you have to manually namespace the statesetInput:(s,input:string)=>({...s,ui:{input}}),increment:s=>({...s,counter:{count:s.counter.count+1}})})

About

Type-safe, composable, boilerplateless reducers

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp