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