转载

如何在前端编码时实现人肉双向编译

React+flux是目前最火的前端解决方案之一,但flux槽点颇多,例如store比较混乱,使用比较繁琐等,于是出现了很多第三方的基于flux优化的架构。

如何在前端编码时实现人肉双向编译

有人统计了目前主流的flux实现方案,感兴趣的可以看这里: Which Flux implementation should I use?

其中 redux 是目前 githubstar 最多的一个方案,该方案完全独立于 react ,意味着这套理念可以作为架构层应用于其他的组件化方案。同时官方也提供了 react-redux 库,帮助开发者直接使用 react+redux 快速开发。

个人理解它的主要特性体现在以下几点:

  1. 强制使用一个全局的 storestore 只提供了几个简单的api(实际上应该是4个),如 subscribe / dispatch (订阅、发布), getState , replaceReducer

  2. store 负责维护一个唯一的叫做 state 树的对象,其中 state 存储了应用需要用到的所有数据。

  3. store 和顶层组件使用 connect 方法绑定,并赋给 props 一个 dispatch 方法,可以直接在组件内部 this.props.dispatch(action) 。 简单一点说,就是去掉了 flux 中组件和 storeunbind/bind 环节。当 state 变化时,自动更新 components ,不需要手动操作。

  4. 提供了 applyMiddleware 方法用于异步的 action ,并且提供了加入中间件的能力,例如打印日志追踪应用的所有状态变化。

  5. 对全局的数据 state 的操作,由多个 reducer 完成。每个 reducer 都是一个纯函数,接收两个参数 stateaction ,返回处理后的 state 。这点类似管道的操作。

接下来我们可以回答标题的问题了,即:如何在前端编码时实现人肉双向编(zi)译(can)。

其实就是使用coffee来编写react+redux应用。<br />

我们来写个简单的hello world玩玩。

view部分

这部分和redux/flux无关,纯粹react的实现,使用jsx的话,render部分的代码大概长这样:

  1. render:function(){ 
  2.      return  ( 
  3.         <div> 
  4.             <div  class = "timer" >定时器:{interval}</div> 
  5.             <div>{title}</div> 
  6.             <input ref= "input" ><button>click it.</button> 
  7.         </div> 
  8.     ) 

那如何使用coffee写这段代码呢? 我们需要先将jsx编译这类似这样的js代码,请注意是用大脑编译:

  1. render:function(){ 
  2.      return  React.createElement( 'div' , null
  3.         React.createElement( 'div' ,{className: 'timer' }, '定时器' + this .props.interval), 
  4.         React.createElement( 'div' , null , this .props.title), 
  5.         React.createElement( 'input' ,{ref: 'input' }), 
  6.         React.createElement( 'button' , null , 'click it.'
  7.     ); 

然后将js代码逆向编译为coffee。

这里我们可以用 $ 代替 React.createElement 简化代码(终于可以用jQuery的坑位了),得益于 coffee 的语法,借助 React.DOM 可以用一种更简单的方式实现:

{<span>div</span>,input,button,span,h1,h2,h3} = React.DOM<br />

这里就不单独放render部分,直接看完整代码:

  1. {Component,PropTypes} = React = require  'react'  
  2. $ = React.createElement 
  3. {div,input,button} = React.DOM 
  4.  
  5. class  App  extends  Component 
  6.     clickHandle:-> 
  7.         dom =  this .refs.input.getDOMNode() 
  8.          this .props.actions.change(dom.value) 
  9.         dom.value =  ''  
  10.     render:-> 
  11.         {title,interval} =  this .props 
  12.         div className: 'timer'
  13.             div  null , '定时器:'  + interval 
  14.             div  null ,title 
  15.             input ref: 'input'  
  16.             button onClick: @clickHandle .bind( this ), 'click it.'  
  17.  
  18. App.propTypes = 
  19.     title: PropTypes.string 
  20.     actions: PropTypes.object 
  21.     interval: PropTypes.number 
  22.  
  23. module.exports = App 

如果你能看到并看懂这段coffee,并在大脑里自动编译成js代码再到jsx代码,恭喜你。

连接store

这个环节的作用,主要是实现view层和store层的绑定,当store数据变化时,可自动更新view。

这里需要使用redux提供的createStore方法创建一个store,该方法接受2个参数,reducer和初始的state(应用初始数据)。

store.coffee的代码如下:

  1. {createStore} = require  'redux'  
  2. reducers = require  './reducers'  # reducer 
  3. state = require  './state'  # 应用初始数据 
  4.  
  5. module.exports = createStore reducers,state 

然后我们在应用的入口将store和App绑定,这里使用了redux官方提供的react-redux库。

  1. {Provider,connect} = require  'react-redux'  
  2. store = require  './store'  
  3. $ = React.createElement 
  4. mapState = (state)-> 
  5.     state 
  6. rootComponent = $ Provider,store:store,-> 
  7.     $ connect(mapState)(App) 
  8. React.render rootComponent,document.body 

可能有人会问,mapState和Provider是什么鬼?

mapState提供了一个类似选择器的效果,当一个应用很庞大时,可以选择将 state 的某一部分数据连接到该组件。我们这里用不着,直接返回 state 自身。

Provider是一个特殊处理过的react component,官方文档是这样描述的:

  1. This makes our store instance available to the components below. 
  2. (Internally,  this  is done via React undocumented “context” feature,  
  3. but it’s not exposed directly in the API so don’t worry about it.) 

所以,放心的用就好了。

connect方法用于连接state和App,之后即可在App组件内部使用this.props.dispatch()方法了。

添加action和reducer

最后我们添加一个按钮点击的事件和定时器,用于触发action,并编写对应的reducer处理数据。

在前面的 App 内部已经添加了 this.props.actions.change(dom.value) ,这里看下action.coffee的代码:

  1. module.exports = 
  2.     change:(title)-> 
  3.         type: 'change'  
  4.         title: title 
  5.     timer:(interval)-> 
  6.         type: 'timer'  
  7.         interval:interval 

再看reducer.coffee

  1. module.exports = (state,action)-> 
  2.      switch  action.type 
  3.         when  'change'  
  4.             Object.assign {},state,title: 'hello '  + action.title 
  5.         when  'timer'  
  6.             Object.assign {},state,interval:action.interval 
  7.          else  
  8.             state 

至此,代码写完了。

一些其他的东西

这里只介绍一个中间件的思想,其他的特性例如异步action,或者dispatch一个promise等原理基本类似:

  1. dispatch = store.dispatch 
  2. store.dispatch = (action)-> 
  3.     console.log action # 打印每一次action 
  4.     dispatch.apply store,arguments 

<br />

由于时间关系,redux的一些特性和设计原理没有展现出来,以后有时间再单独讲,完整的项目代码,感兴趣的同学可以看这里: 请点我 ,或者直接找我聊。

项目用到了fis3作为构建工具,使用 fis3 release 即可在本地查看效果。

正文到此结束
Loading...