网页前端状态管理库Redux学习笔记(一)
最近在博客园上看到关于redux的博文,于是去了解了一下。
这个Js库的思路还是很好的,禁止随意修改状态,只能通过触发事件来修改。中文文档在这里。
前面都很顺利,但是看到异步章节,感觉关于异步说得很乱,而且必须配合插件才能实现异步。我是不喜欢用插件的人,能不用则不用。因此自己写了一个异步解决方案。大致的思路如下:
- 只在一个函数doSometing中处理异步方法和处理返回值,使用action传递执行类型参数
- 需要执行异步方法时,使用action传递参数,指示函数执行异步,然后返回特定状态,例如字符串"fetching"
- 在异步方法的回调中再次触发此状态修改(执行store.dispatch)
- 需要执行处理返回的数据时,使用action传递参数,指示函数执行数据处理,然后返回正确的状态值
- 最后,指定在哪里处理正确的状态值,可以在状态对象中订阅事件,也可以在第3步中直接处理
最后,贴上我写的例子,欢迎指教:)
<!doctype html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <title>Redux Demo</title> <style> html,body{ height: 100%; overflow: hidden; margin: 0; } #box{ max-height: 85%; overflow-x: hidden; overflow-y: auto; padding: 0; margin: 0; list-style-type: none; } #box>li:first-child{ margin-top: 0;; } #box>li:last-child{ margin-bottom: 0; } li{ background: #f23; padding: 5px 10px; margin: 5px; } #btn{ width: 120px; height: 10%; margin: 2.5% calc(50% - 60px); } dialog::backdrop { background: rgba(0,0,0,0.9); } </style> </head> <body> <button id="btn">click btn</button> <ul id="box"></ul> <dialog id="dial"></dialog> <script type="text/javascript" src="http://cdn.bootcss.com/redux/3.5.2/redux.min.js" ></script> <script type="text/javascript"> var store; Type1(); btn.addEventListener("click",function() { store.dispatch({type:"act",doAddItem:true}); }); function renderListItem(index, callback) { box.innerHTML+="<li><p>user "+index+"</p><p>your words here.</p></li>"; !!callback && callback(); } function showLoading (msg, isCanClose) { dial.innerHTML=msg; !dial.open && dial.showModal(); isCanClose && (dial.onclick=function(){this.close();dial.onclick=null;}); } function hideLoading () { dial.close(); } function Type1(){ var FLAGS={ INCREMENT:"i", DECREMENT:"d", SAYHI:"s" }; function counter(state, action) { !state && (state=1); return (action===FLAGS.INCREMENT)?(++state):(--state); } function sayHello(state, action) { state=!!action.name?action.name:"robin"; return (action.word||"hello")+", "+state; } function asyncCall (callback) { window.setTimeout(function () { callback && callback(); },3000); } function addListItem (state, param) { if(typeof param.asyncCall!=="number"){ showLoading("waiting for fetching......"); (typeof state!=="number") && (state=-1); asyncCall((function (state) { return function () { store.dispatch({type:"act",doAddItem:{asyncCall:state}}); }; })(state)); return "fetching"; }else{ var msg; state=param.asyncCall; (~~(10*Math.random()))%2===0 && (msg="internal error"); if(!msg){ renderListItem(++state, hideLoading); }else{ showLoading("error : "+msg, true); } return state; } } function Reducers (state, action) { !state && (state={}); !!action.doSayHi && (state.sayHello=sayHello(state.sayHello, action.doSayHi)); !!action.doCounter && (state.counter=counter(state.counter, action.doCounter)); !!action.doAddItem && (state.itemCount=addListItem(state.itemCount, action.doAddItem)); return state; } store=Redux.createStore(Reducers); store.subscribe(()=>{ var state=store.getState(); console.log("state changed to : ", state); }); console.log("init state is : ",store.getState()); function action(conf){ var res={type:"action"}; if(!!conf){ !!conf.doSayHi && (res.doSayHi={name:conf.doSayHi.name, word:conf.doSayHi.word}); !!conf.doCounter && (res.doCounter=conf.doCounter); !!conf.doAddItem && (res.doAddItem=conf.doAddItem); } return res; } var act=action({ doSayHi:{ name:"Gant", word:"Good noon" } }); store.dispatch(act); act=action({ doCounter:FLAGS.INCREMENT }); store.dispatch(act); act=action({ doSayHi:{ name:"Rassual", word:"Good morning" } }); store.dispatch(act); act=action({ doSayHi:{ name:"dear dd", word:"Good evening" }, doCounter:FLAGS.DECREMENT }); store.dispatch(act); } </script> </body> </html>