1. IMVC的I是Isomorphic的縮寫,意思是同構,指一份JS代碼既可以在Node.js運行也可以在Browser里運行
1》M是Model,指狀態以及狀態變化的函數的集合,由initialState狀態和actions函數組成
2》V是View的縮寫,React組件
3》C是Controller的縮寫,指包含生命周期方法、事件處理器、同構工具方法及負責同步View和Model的中間媒介
4》在react-imvc的Model中state是immutable data、action是pure function,View是React.js建議盡可能使用function stateless component寫法。所有跟外界的交互比如:Life-Cycle method、Ajax/Fetch、Event Handler時間處理器、localStorage等都放在Controller里。
2. Controller的一些重要屬性
1》View是React Component組件,該組件的props結構如下:
props.state是controller.store.getState()里的global state狀態樹
props.handlers是controller實例里以handleXXX形式定義的事件處理器的集合對象
props.actions是controller.store.actions里的actions集合對象
2》Model屬性是一個對象,除了initialState屬性外其余都是pure function
3》preload對象用來在頁面顯示前預加載css、json等數據
4》handlers在初始化時從controller的實例里收集以handle開頭以箭頭函數形式定義的方法的集合對象,用來傳遞給controller.View組件
3. Controller的一些重要方法
1》Event handler
import React from 'react' import Controller from 'react-imvc/controller' export default class extends Controller { View = View initialState = { count: 0, } actions = { INCREMENT: state => ({ ...state, count: state.count + 1 }), DECREMENT: state => ({ ...state, count: state.count - 1 }), CHANGE_BY_NUM: (state, num) => ({ ...state, count: state.count + Number(num) }) } // 事件處理器必須使用 arrow function 箭頭函數的語法 handleIncre = () => { let { INCREMENT } = this.store.actions INCREMENT() } // 事件處理器里使用 action 更新 global state handleDecre = () => { let { DECREMENT } = this.store.actions DECREMENT() } // 將特殊的索引如 index, id 或者其他信息,緩存在 DOM attribute 里 // 在事件處理器里,從 DOM attribute 里取回 handleCustomNum = event => { let { CHANGE_BY_NUM } = this.store.actions let num = event.currentTarget.getAttribute('data-num') CHANGE_BY_NUM(num) } } /** * 在 view 組件里,可以從 props 里拿到 global state 和 global event handlers */ function View({ state, handlers }) { let { handleIncre, handleDecre, handleCustomNum } = handlers return ( <div> <h1>Count: {state.count}</h1> <button onClick={handleIncre}>+1</button> <button onClick={handleDecre}>-1</button> <button onClick={handleCustomNum} data-num={10}>+10</button> </div> ) }
2》handleInputChange(path, value, oldValue) -> final value
3》Style組件將controller.preload里配置的css展示在頁面上
import React from 'react' import Controller from 'react-imvc/controller' import { Style } from 'react-imvc/component' // 加載 Style 組件 export default class extends Controller { preload = { 'main': 'path/to/css' // 配置 css 文件路徑 } View = View } // 當組件渲染時,Style 標簽會將 preload 里的同名 css 內容,展示為 style 標簽。 function View() { return ( <div> <Style name="main" /> </div> ) }
4》Input組件用來將表單跟store聯系起來
import React from 'react' import Controller from 'react-imvc/controller' import { Input } from 'react-imvc/component' // 加載 Input 組件 export default class extends Controller { View = View // 可以在 Controller 里直接寫 initialState initialState = { // 多層次對象 user: { name: { first: '', last: '', }, email: '', age: 0 }, // 數組對象 friends: [{ name: 'friendA', }, { name: 'friendB', }], // 復合對象 phone: { value: '', isValid: false, isWarn: false, }, content: '' } } /** * Input 組件支持 path 寫法,支持數組 * 可以用 .:/ 三種分隔符書寫 path * 不需要寫 value,Input 組件會使用以下屬性: * 1》使用 transformer 屬性可以在更新 store 之前做數據處理,接受兩個參數 transformer(newValue, oldValue),其返回值將作為最后更新到 store 的 value。 * 2》使用 check 屬性,可以驗證字段。當 Input 組件傳入了 check 屬性時,它將被視為復合對象 { value, isValid, isWarn } 三個屬性,它有以下行為: * - 當用戶 blur 脫離表單焦點時,使用 check 函數檢查 value 值,如果 check 函數返回 true,則 isValid = true,isWarn = false。 * - 當用戶 focus 聚焦表單時,取消 isWarn = false 的狀態。 * - 在將 input.value 更新到 store 時,會自動補全 `${name}.value` 更新 state。 * 3》使用 as 屬性,可以自定義渲染標簽.input 組件默認渲染為 input 標簽,可以使用 as 屬性將它渲染成 textarea 標簽或其他可以觸發 onChange 方法的組件。 */ function View({ state }) { return ( <div> firstname: <Input name="user.name.first" /> lastname: <Input name="user:name:last" /> email: <Input name="user/email" /> age: <Input name="user.age" transformer={Number} > friends: { state.friends.map((friend, index) => { return ( <div> name: <Input name={`friends/${index}/name`} /> </div> ) }) } phone: <Input name="phone" check={isValidPhone} /> content: <Input as="textarea" name="content" /> </div> ) }
3. Controller中重要的生命周期方法
.》shouldComponentCreat()方法觸發時view還未被創建渲染,主要用於鑒定權限,如果用戶沒有權限訪問該頁面,可以通過 this.redirect 方法,重定向到其他頁面。
.》componentWillCreate()方法觸發時view還未被創建渲染,可在方法內調用接口獲取首屏數據
.》componentDidFirstMount() 方法觸發時用戶已經看到了首屏,可在方法內調用接口獲取非首屏數據。
.》componentDidMount()方法觸發時component已經mount到頁面,可在方法內進行DOM操作等瀏覽器相關活動
.》componentWillUnmount()方法觸發時component即將從頁面unmount,解綁計時器等跟componentDidMount相關的逆操作
.》stateDidChange(data)方法觸發時store里的state發生變化並且view也重新渲染,data中為actionType, actionPayload, previousState, currentState
4. 高階組件connect(selector)(ReactComponent)
connect是一個高階函數,第一次調用時接受selector函數作為參數返回withData函數。withData函數接受一個React組件作為參數返回一個新的React組件。withData會將selector函數返回的數據作為props傳入新的React組件。selector({state, handlers, actions})函數得到一個data參數包含三個字段,分別對應controller里的global state, global handlers和actions對象
import React from "react"; import connect from 'react-imvc/hoc/connect' const withData = connect(({ state }) => { return { content: state.loadingText } }) export default withData(Loading) function Loading(props) { if (!props.content) { return null; } return ( <div id="wxloading" className="wx_loading"> <div className="wx_loading_inner"> <i className="wx_loading_icon" /> {props.content} </div> </div> ); }
