github地址javascript
咱們將dingDang定義爲一個產品而不是一個框架亦或工具包 。 由於dingDang在設計之初就將用戶的開發體驗放在第一位,而性能問題則放在第二位。 固然這並不意味着咱們就是一個性能很糟糕的產品,只是說咱們會在性能可以接受的範圍內儘量的去讓開發者的開發體驗更好。 事實上dingDang的內部數據所有都採用了immutable數據類型。 dingDang中使用了大量的 Promise 於 immutableJS , 若是你對此不太熟悉,能夠先熟悉一下 這兩個知識點。
DingDang裝飾器 , 一切故事的開始 , 目前 僅僅只有 sceneStorage 一個參數 。 主要用來定義場景的臨時數據存儲方案。 好比說: 在react-web 中 你應該採用sessionStorage 或者 localStorage , 而在react-native 中,你應該採用AsyncStorage 。 在sceneStorage中的3個函數 全部的返回值 要求是標準的 JS Object , set 方法的 key 咱們也會傳輸一個標準的 JS Object
@DingDang({ sceneStorage: { set: (k, v) => sessionStorage.setItem(k, JSON.stringify(v)), get: (k) => JSON.parse(sessionStorage.getItem(k)), del: (k) => sessionStorage.removeItem(k) } }) class App extends React.Component { render() { return ( <HashRouter> <Layout> </Layout> </HashRouter> ) } } render( <App />, document.querySelector("#app") )
const store = { namespace: 'demoStore', state: {}, rules: {}, reducer: {}, effect: {}, initialization: () => false, destroy: () => false }
事實上,store中只有namespace字段是必須的,其餘的字段若是你不須要的話,能夠不寫。
該屬性的值是一個字符串 , 在dingDang中請務必保證namespace的惟一性,由於咱們須要以此爲依據去斷定用戶是否進入了一個全新的場景。
該屬性中主要用來存儲store中的全部數據集 , 通常來講dingDang會將這裏的數據都轉換爲immutable類型。
首先讓咱們來看一個完整的rules的案例:
export default { namespace: 'demoStore', state: { name: '張三' }, rules: { name: [ { validate: (state, name) => name ? true : false, error: '用戶姓名不能爲空', miss:false }, { validate: (state, name) => name.length < 6, error: '用戶姓名最大長度不能超過6!', miss:true } ] } }
rules 中配置的實際上是針對state 中數據的校驗機制 ,好比在咱們的state 中有name這個字段 , 那麼你能夠在 rules中也配置一個 對應的name字段 , 可是在rules中 , 該字段的值必須爲數組, 具體的校驗規則,咱們會按照數據的前後順序來執行。 數據中存儲的是每個 獨立的 rule ,一個標準的 rule以下:
{ validate: (state, name) => name ? true : false, error: '用戶姓名不能爲空', miss:false }
validate : 值爲一個判斷函數 , 該函數咱們會自動的注入全量的state參數, 以及你即將賦予字段的新值 , 例如:name 。 這裏的校驗邏輯爲: 若是知足你所需的格式 ,那麼返回true , 不然false error: 錯誤提示 , 在執行validate獲得false以後拋出的異常信息 miss : 定義是否丟失, 這裏的含義是指, 若是即將賦予的新值不能知足校驗條件, 那麼新的值是否丟失掉(即不存儲進state) 如不定義該字段, 那麼該字段默認爲false , 即不丟失 ,依然賦予state。
該字段內存儲的應該都是純函數 ,在這裏你能夠直接去針對state賦予全新的值 , 首先讓咱們來看一段相對完整的reducer代碼案例:
export default { namespace: 'orderStore', state: { goodsList: [ { id: 1, name: '方便麪', price: 2 }, { id: 2, name: '筆記本電腦', price: 3281 } ], cart: [] } reducer: { addToCart: (state, goods) => { return state.set('cart', state.get('cart').push(goods)); } } }
正如上面的demo所示 , 咱們會爲reducer 中的每個函數的 第一個參數都注入一個全量的 immutable 類型的state , 而第二個參數則是你在調用的時候傳入的。咱們對reducer的要求是,最後你必須返回一個你指望的、全新的、全量的state。 下面咱們看一段調用reducer 的案例代碼:
@Page(Store) export default class GoodsList extends React.Component { static injectProps = { goodsList: [], cart: [] } _addToCart = async (n) => { const {execReducer} = this.props; try { await execReducer('addToCart')(n) } catch (err) { console.log(err) } } render() { const {injectProps: {goodsList}} = this.props; return ( <Layout> <Row> <Col> <Table rowKey={'id'} dataSource={goodsList}> <Column title="商品名稱" dataIndex="name"/> <Column title="商品價格" dataIndex="price"/> <Column title="操做" render={ (n) => <a href="javascript:void(0);" onClick={ () => this._addToCart(n) }>加入購物車</a> }/> </Table> </Col> </Row> <Row> <Col> <Link to="/balance"><Button>去結算</Button></Link> </Col> </Row> </Layout> ) } }
上述的demo中 , 咱們但願你不要被過多的去關注可能對於你來講未知的代碼影響,你只須要關注咱們但願闡述的內容便可(即_addToCart函數), 首先咱們應該明確_addToCart函數是一段調用reducer的代碼 , 其次咱們觀察到 , _addToCart函數採用了async await 的方式來書寫, 說明該函數是異步的 , 而且返回了一個Promise對象給咱們 。 事實上,在你執行reducer時,dingDang內部有可能會去rules中取到對應的規則進行校驗,若是出現異常的話會reject出來給你 , 因此這裏 catch到的err實際上是你的rules中所配置的信息 , 同時若是你的reducer沒有任何毛病, dingDang依然會有返回值給你,這個返回值是reducer 以後的一個全新的state。
咱們將這裏定義爲反作用的 , 也就是但願你將全部的非純函數在這裏定義(依賴於外部因素的函數,相同輸入並不是能保障獲得相同的結果 ,好比調用API)。 慣例,讓咱們看一段完整的effect案例:
export default { namespace: 'orderStore', state: { goodsList: [], cart: [] } reducer: { addToCart: (state, goods) => { return state.set('cart', state.get('cart').push(goods)); } }, effect:{ queryGoodsList: async ({resolve , reject , state , execReducer , onChangeState} , params) => { const result = await queryGoodsListByAPI(params); if( result.success ){ await onChangeState(result.goodsList).catch(err => reject(err)); resolve(true) }else { reject( result.error ) } } } }
effect 的參數,咱們注入的比較多, resolve 、 reject 這裏就不做解釋了 , 若是你不是很清楚, 建議去溫習一下Promise 。state 也不做過多的解釋了。 這裏咱們稍微來提一下 execReducer 和 onChangeState 這兩個函數 。 execReducer 在前面咱們也見過 , 這個函數屬於dingDang的系統級函數,主要用來 輔助你去調用reducer。 onChangeState 也是一個dingDang系統級函數 , 這個函數是便於你直接去修改state中的數據 。 咱們注意區分一下reducer 和 onChangeState 的區別 , 通常來講reducer的調用方式以下:
execReducer('addToCart')(params);
而onChangeState的調用方式
onChangeState(jsObjectParams)
咱們總結歸納一下,reducer實際上是你用來去調用你本身聲明的指定的一個reducer函數 , 而onChangeState 實際上是直接去修改的state中的值 。 通常來講, 若是你無需作業務邏輯, 僅僅只是想把一個數據放進state , 咱們推薦你使用 onChangeState 的方式, 請不要擔憂rules 的校驗問題 onChangeState 依然 會去執行rules的校驗代碼 , 而若是您不只僅是將一個數據放進state , 在這中間可能還有不少業務邏輯代碼, 那麼咱們推薦你本身去寫一個reducer , 而後使用 execReducer 來進行調用
initialization 是一個初始化函數 , 這裏不想過多的說什麼 , 主要重點說一下 , 咱們爲這個函數注入了 execReducer 、 execEffect 、 onChangeState
與initialization同理 , 注入的參數也相同
高階函數 , 主要用來調用 reducer , 返回Promise
高階函數 , 主要用來調用 effect , 返回Promise
簡化修改state中值的方式
用來裝飾一個頁面的入口 ,裝飾以後的頁面 可使用聲明式注入 demo案例以下
@Page(store) class Home extends React.Component { static injectProps = { initName: null, name: null } render() { const {injectProps: {initName, name}, onChangeState} = this.props; return ( <div> <h1> {initName} </h1> <h1> {name} </h1> <button onClick={() => onChangeState({initName: '首屏文字被變化以後'})}>變首屏文字</button> <Card/> </div> ) } }
用來裝飾一個頁面下的子組件,裝飾以後的頁面 可使用聲明式注入 demo案例以下
@Component class Card extends React.Component { constructor(props) { super(props) this.state = { error: '' } } static injectProps = { name: null } render() { const {injectProps: {name}} = this.props; return ( <div> {name}<span style={{marginLeft: 20, color: 'red'}}>{this.state.error}</span> <button onClick={this._onChangeName}>變換</button> </div> ) } _onChangeName = () => { const {onChangeState} = this.props; onChangeState({name: '李四李四李四李四李四李四'}).catch(e => this.setState({error: e})) } }
在組件中使用 static injectProps 的方式能夠來聲明 讓dingDang 從store中爲你注入哪些數據。
在某些業務背景下,咱們可能須要多個頁面來完整一組業務。 假設咱們須要 A B C 3個頁面來完成一組業務 , 在這組業務中 數據其實基本上是相同的, 針對數據的某些業務操做也是相同的。 在這種背景下,咱們提出了場景的概念。 咱們對場景的定義:多個頁面之間針對同一組業務數據進行的一連串行爲動做來完成的一組業務,咱們稱之爲場景。 普通的Page裝飾器裝飾以後的頁面只能取本身獨立的那份store , 而使用ScenePage裝飾以後的多個頁面 , 只要綁定的Store 的namespace相同,那麼這個Store的聲明週期將一直存在,直到用戶跳出當前場景。
npm install 0.1.1-alpha --save