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> ); }