- Notifications
You must be signed in to change notification settings - Fork7
A light-weight type-safe Elm-like alternative for Redux ecosystem, inspired by hyperapp and Elmish
License
hydux/hydux
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
A light-weight Elm-like alternative for Redux ecosystem, inspired byHyperapp andElmish.
- Elm Architecture, split your whole app withinit,state,actions.
- hyperapp compatible API
- Elm-likeside effect manager and subscribe API
- Support any vdom library, including react (official support)
- Router (Recommended)
- Official support for react-router
- Hot reload (hmr)
- Server-Side Rendering(SSR)
- code splitting, seamlessly integrated with SSR.
- logger, persist,Redux Devtools with time traveling,ultradom(1kb vdom),**All in One**, easily setup all these fancy stuff without pain!
yarn add hydux# or npm i hydux
This is an experimental dependency injection API for actions inspired by react-hooks, totally downward compatible, with this we don't need curring to inject state and actions or manually markup types for the return value any more!
yarn add hydux@^v0.5.8
import{inject}from'hydux'exportdefault{init:()=>({count:1}),actions:{down(){let{ state, actions, setState, Cmd}=inject<State,Actions>()setState({count:state.count-1})Cmd.addSub(_=>_.log('down -1'))actions.up()},up(){let{ state, actions}=inject<State,Actions>()return{count:state.count+1}},log(msg){console.log(msg)}},view:(state,actions)=>{return(<div><h1>{state.count}</h1><buttononclick={actions.down}>–</button><buttononclick={actions.up}>+</button></div>)}}
Let's say we got a counter, like this.
// Counter.jsexportdefault{init:()=>({count:1}),actions:{down:()=>state=>({count:state.count-1}),up:()=>state=>({count:state.count+1})},view:(state:State,actions:Actions)=><div><h1>{state.count}</h1><buttononclick={actions.down}>–</button><buttononclick={actions.up}>+</button></div>}
Then we can compose it in Elm way, you can easily reuse your components.
import_appfrom'hydux'importwithUltradom,{h,React}from'hydux/lib/enhancers/ultradom-render'importCounterfrom'./counter'// use built-in 1kb ultradom to render the view.letapp=withUltradom()(_app)constactions={counter1:Counter.actions,counter2:Counter.actions,}conststate={counter1:Counter.init(),counter2:Counter.init(),}constview=(state:State,actions:Actions,)=><main><h1>Counter1:</h1>{Counter.view(state.counter1,actions.counter1)}<h1>Counter2:</h1>{Counter.view(state.counter2,actions.counter2)}</main>exportdefaultapp({init:()=>state, actions, view,})
You can init the state of your app via plain object, or with side effects, like fetch remote data.
import*asHyduxfrom'hydux'const{ Cmd}=Hyduxexportfunctioninit(){return{state:{// pojo statecount:1,},cmd:Cmd.ofSub(// update your state via side effects._=>fetch('https://your.server/init/count')// `_` is the real actions, don't confuse with the plain object `actions` that we created below, calling functions from plain object won't trigger state update!.then(res=>res.json()).then(count=>_.setCount(count)))}}exportconstactions={setCount:n=>(state,actions)=>{return{count:n}}}
If we want to init a child component with init command, we need to map it to the sub level via lambda function, just liketype lifting in Elm.
// App.tsximport{React}from'hydux-react'import*asHyduxfrom'hydux'import*asCounterfrom'Counter'constCmd=Hydux.Cmdexportconstinit=()=>{constcounter1=Counter.init()constcounter2=Counter.init()return{state:{counter1:counter1.state,counter2:counter2.state,},cmd:Cmd.batch(Cmd.map((_:Actions)=>_.counter1,counter1.cmd),// Map counter1's init command to parent componentCmd.map((_:Actions)=>_.counter2,counter2.cmd),// Map counter2's init command to parent componentCmd.ofSub(_=>// some other commands of App))}}exportconstactions={counter1:Counter.actions,counter2:Counter.actions,// ... other actions}exportconstview=(state:State,actions:Actions)=>(<main><h1>Counter1:</h1>{Counter.view(state.counter1,actions.counter1)}<h1>Counter2:</h1>{Counter.view(state.counter2,actions.counter2)}</main>)exporttypeActions=typeofactionsexporttypeState=ReturnType<typeofinit>['state']
This might be too much boilerplate code, but hey, we provide a type-friendly helper function! See:
// Combine all sub components's init/actions/views, auto map init commands.constsubComps=Hydux.combine({counter1:[Counter,Counter.init()],counter2:[Counter,Counter.init()],})exportconstinit2=()=>{return{state:{ ...subComps.state,// other state},cmd:Cmd.batch(subComps.cmd,// other commands)}}exportconstactions={ ...subComps.actions,// ... other actions}exportconstview=(state:State,actions:Actions)=>(<main><h1>Counter1:</h1>{subComps.render('counter1',state,actions)}// euqal to:// {subComps.views.counter1(state.counter1, actions.counter1)}// .render('<key>', ...) won't not work with custom views that not match `(state, actions) => any` or `(props) => any` signature// So we still need `.views.counter1(...args)` in this case.<h1>Counter2:</h1>{subComps.render('counter2',state,actions)}</main>)
This library also implemented a Elm-like side effects manager, you can simple return a record with state, cmd in your actione.g.
importapp,{Cmd}from'hydux'functionupLater(n){returnnewPromise(resolve=>setTimeout(()=>resolve(n+10),1000))}app({init:()=>({count:1}),actions:{down:()=>state=>({count:state.count-1}),up:()=>state=>({count:state.count+1}),upN:n=>state=>({count:state.count+n}),upLater:n=>(state,actions/* actions of same level */)=>({ state,// don't change the state, won't trigger view updatecmd:Cmd.ofPromise(upLater/* a function with single parameter and return a promise */,n/* the parameter of the funciton */,actions.upN/* success handler, optional */,console.error/* error handler, optional */)}),// Short hand of command onlyupLater2:n=>(state,actions/* actions of same level */)=>Cmd.ofPromise(upLater/* a function with single parameter and return a promise */,n/* the parameter of the funciton */,actions.upN/* success handler, optional */,console.error/* error handler, optional */),},view:()=>{/*...*/},})
In Elm, we can intercept child component's message in parent component, because child's update function is called in parent's update function. But how can we do this in hydux?
import*asassertfrom'assert'import*asHyduxfrom'../index'importCounterfrom'./counter'const{ Cmd}=Hyduxexportfunctioninit(){return{state:{counter1:Counter.init(),counter2:Counter.init(),}}}constactions={counter2:counter.actions,counter1:counter.actions}Hydux.overrideAction(actions,_=>_.counter1.upN,(n:number)=>(action,ps:State,// parent state (State)pa,// parent actions (Actions)// s: State['counter1'], // child state// a: Actions['counter1'], // child actions)=>{const{ state, cmd}=action(n+1)assert.equal(state.count,ps.counter1.count+n+1,'call child action work')return{ state,cmd:Cmd.batch(cmd,Cmd.ofFn(()=>pa.counter2.up()))}})typeState=ReturnType<typeofinit>['state']typeActions=typeofactionsletctx=Hydux.app<State,Actions>({init:()=>initState, actions,view:noop,onRender:noop})
- hydux-react: Hydux's react integration
- hydux-preact: Hydux'spreact integration
- hydux-react-router: Hydux's react-router integration
- hydux-mutator: A statically-typed immutable update help package, which also contains immutable collections.
- hydux-transitions: A css transition library inspired byanimajs's timeline, followThe Elm Architecture.
- hydux-data: Statically-typed data-driven development for hydux, in the Elm way. Inspired by apollo-client.
- hydux-pixi: High performancepixi.js renderer for Hydux.
- hydux code snippets for vscode (TS) Best practice for using hydux + typescript without boilerplate code.
- samples-antd: Admin sample in hydux.
git clone https://github.com/hydux/hydux.gitcd hyduxyarn# or npm icd examples/counteryarn# or npm inpm start
Now openhttp://localhost:8080 and hack!
After tryingFable +Elmish for several month, I need to write a small web App in my company, for many reasons I cannot choose some fancy stuff likeFable +Elmish, simply speaking, I need to use the mainstream JS stack but don't want to bear Redux's cumbersome, complex toolchain, etc anymore.
After some digging around, hyperapp looks really good to me, but I quickly find out it doesn't work with React, and many libraries don't work with the newest API. So I create this to supportdifferent vdom libraries, like React(official support),ultradom(built-in), Preact,inferno or what ever you want, just need to write a simple enhancer!
Also, to avoid breaking change, we havebuilt-in support for HMR, logger, persist,Redux Devtools, you know you want it!
MIT
About
A light-weight type-safe Elm-like alternative for Redux ecosystem, inspired by hyperapp and Elmish