深入浅出React和Redux

第1章 React新的前端思维方式

1.1 初始化react项目 create-react-app

React依赖于一个庞大的技术栈:比如转译Javascript代码需要Babel,模块打包工具需要webpack,定制build过程需要grunt或者gulp,技术栈各自需要配置文件

1.2 组件Component

  1. React首要思想是通过组件来开发应用
  2. import是ES6语法中倒入文件模块的方式,ES6语法的Javascript代码会被webpack和babel转译成所有浏览器都支持的ES5语法
  3. Component作为所有组件的基类

1.2.1 JSX

JSX使我们在Javascript中像HTML一样编写,e.g ReactDOM.render的第一个参数

  1. 在JSX中使用的“元素”不局限于HTML中的元素,可以是任何一个React组件,根据第一个字母是否大写判断是否为React元素
  2. JSX中通过onclick给元素添加事件处理函数,onclick控制在组件范围内,将onclick挂载在最顶端的DOM节点上

最顶端DOM节点添加一个事件处理函数,使用事件委托(event delegation)方式处理,所有点击事件被事件处理函数捕获,
然后根据具体组件分配给特定函数

1.3 分解React应用

  1. 所有的配置文件设置在node_modules/react=scripts目录下
  2. build:创建生产环境优化daima
    test:单元测试
    eject:弹射命令,一系列技术栈配置弹射到顶层;但是命令不可逆,建议备份
  3. babel
    1
    2
    3
    4
    5
    6
    //webpack.config.dev.js
    {
    test: /\.(js|jsx)/
    ......
    loader: 'babel'
    }

所有以js或者jsx为扩展名的文件,都由babel处理

1.4 React的工作方式

1.4.1 jQuery如何工作 $

jQuery逻辑:1.根据CSS规则找到id为clickCount按钮;2.挂上一个匿名事件处理函数;3.事件处理函数中选中需要修改的DOM元素;4.读取文本值并加以修改;5.修改这个DOM元素
缺点:项目庞大时代码容易纠缠

1.4.2 React如何工作

关注于“我想要显示什么”,UI = render(data)
React通过重复渲染来实现用户交互,每次渲染只重复渲染最少的DOM元素

第五章关注对比过程

JSX会被Babel解析为一条条创建React组件或者HTML元素的语句

DOM树是对HTML的抽象,Virtual DOM是对DOM树的抽象;DOM不触及浏览器的部分,只存在于Javascript空间的树形结构

事件 –> render –> Virtual DOM –> DOM修改

第二章 设计高质量的React组件

组件设计原则:高内聚、低耦合

2.2 组织组件数据prop和state

2.2.1 prop

  1. prop为组件的对外接口,state为组件的内部状态
    prop属性不限于纯,数据prop的类型不是字符类型时,JSX中必须用{}把prop包住,所以style两层{}

    1
    2
    //外层{},JSX语法;内层{},代表对象常量
    style = {{color: "red"}}
  2. constructor函数
    给this.props赋值是React.Component构造函数的工作之一
    定义super(props),进而类实例的所有成员函数可以通过this.props访问父组件传递的props值

  3. 解构赋值

    1
    const {caption} = this.props
  4. propType检查:组件声明接口规范
    propTypes能够发现开发阶段问题,不建议在产品阶段,e.g babel-react-optimize

    1
    2
    3
    4
    Counter.propTypes = {
    caption: PropTypes.string.isRequired,
    initValue: PropTypes.number //无isRequired,非必须属性
    }

2.2.2 state:存在的目的是让组件来改变

  1. 组件的state必须是一个Javascript对象,不能是string或者number这样的简单数据
  2. React的defaultProps功能:优化isRequired的频繁判断
  3. 组件内部修改this.state,并没有驱动组件进行重新渲染,调用this.setState改变组件状态

2.3 组件生命周期

组件第一次渲染:constructor –> getInitialState –> getDefaultProps –> componentWillMount –> render –> componentDidMount

  1. constructor
    无状态的React组件往往不需要定义构造函数
    目的:(1)初始化state;(2)绑定成员函数的this环境,this始终指向当前组件实例
    类实例this.onClickIncrementButton = this.onClickIncrementButton.bind(this)
  2. getInitialState和getDefaultProps
    getInitialState:只有在React.createClass方法创造的组件中才能初始化组件的this.state
    getDefaultProps: 返回值作为props初始值

    1
    2
    3
    4
    5
    6
    7
    //ES6 在构造函数中通过this.state赋值完成状态的初始化
    class Sample extends React.component {

    }
    Sample.defaultProps = {

    }
  3. render:纯函数(根据this.props和this.state)
    render并不做实际的渲染动作,只是返回一个JSX描述的结构,最终由React来操作渲染过程

  4. componentWillMount和componentDidMount
    调用关系:componentWillMount –> render –> componentDidMount
    componentWillMount:“将要装载”,没有任何渲染出来的结果,所有可以在componentWillMount中做的事情,都可以提前到constructor中去做,可以在服务器端和浏览器端调用

服务器端js:Node.js
componentDidMount:render函数调用完之后,componentDidMount函数并不会立即调用,componentDidMount被调用的时候,render函数返回的东西已经引发了渲染,组件已经“装载”到DOM树上,只在浏览器端被调用

  1. React和其他UI库的结合

d3.js已经支持了丰富的绘制图表的功能
React+jQuery:利用componentDidMount函数,此时React的DOM组件对应的DOM已经存在,所有的事件处理函数已经设置好,可以调用jQuery在已经绘制号的DOM基础上增强新的功能

组件更新过程:componentWillReceiveProps –> shouldComponentUpdate –> componentWillUpdate –> render –> componentDidUpdate

  1. componentWillReceiveProps(nextProps)
    (1)在render函数里面被渲染的子组件就会经历更新过程,不管父组件传给子组件的props有没有改变,都会触发子组件的componentWillReceiveProps函数

this.setState方法不会触发这个过程,因为函数会根据新的props值来计算是不是要更新内部状态state,更新内部状态的方法就是this.setState,若调用就形成死循环

(2)在JSX中直接把匿名函数赋值给onclick,并不提倡,每次渲染都会创造一个新的匿名对象,可能会引发子组件不必要的重新渲染

1
<button onclick = { () => this.forceUpdate() }></button>

(3)nextProps代表着一次渲染传入的props的值,this.props代表的是上一次渲染的props值,只有两者有变化时才有必要调用this.setState更新内部状态

  1. shouldComponentUpdate(nextProps,nextState):定制函数
    决定了一个组件什么时候不需要渲染
    在组件添加shouldComponentUpdate函数,就沿用所有React组件父类React.Component中的默认实现方式(即简单的返回true)
  2. componentWillUpdate和componentDidUpdate
    两个函数将render夹在中间
    若componentDidMount调用jQuery,则重新渲染时在coomponentDidUpdate函数再次调用jQuery

卸载过程

componentWillUnmount: 和componentDidMout有关,把创造的DOM元素清理掉

组件向外传递数据

函数是一等功名,函数本身可以被看做一种对象

React组件state和Prop的局限

每个Counter组件由自己的状态记录当前计数,而父组件ControlPanel也有一个状态来存储所有Counter计数总和,即数据重复
把数据源放在React组件之外形成全局状态,把各个组件保持和全局状态一致

第三章 从Flux到Redux

3.1 Flux

  1. MVC框架
    分为四个部分:Dispatcher,Store,Action,View
    Dispatcher:处理动作action分发,维持Store之间的依赖关系
    Store:负责存储数据和处理数据相关逻辑
    Action:驱动Dispatcher的Javascript对象
    View:视图
    数据流向:Action –> Dispatcher –> Store –> View —Action—> (Dispatcher)
  2. 创建Flux
    (1)Dispatcher:几乎所有应用都只需拥有一个Dispatcher
    (2)action:纯粹的数据,必须有type字段
    定义需要两个文件:一个定义action类型,一个定义action的构造函数
    1
    2
    3
    4
    AppDispatcher.dispatch({
    type: ActionType.INCREMENT,
    counterCaption: counterCaption
    })

(3)Store:为一个对象,每一个Store都是一个全局惟一的对象,存储应用状态,同时还要接受Dispatcher派发的动作,根据动作决定是否要更新应用
Store只有注册到Dispater实例上才能真正发挥作用

注册过程:把CounterStore注册到全局惟一的Dispatcher上。Dispatcher有一个函数为register,接受一个回调函数作为参数,返回值为token,token可用于Store之间的同步,保存在dispathchToken字段上。

1
2
3
AppDispatcher.register((action) => {

})

当通过register函数把一个回调函数注册到Dispatcher之后,所有派发给Dispatcher的action对象,都会传递到这个回调函数中来,回调函数要做的,就是根据action对象来决定该如何更新自己的状态
(4)View
存在于Flux框架中的React组件需要实现以下的功能:
a.创建时读取Store上状态来初始化组件内部状态
b.当Store上状态发生变化,组件立即同步更新内部状态保持一致
c.View如果改变Store状态,必须且只能派发action

  1. Flux不足
    (1)Store之间依赖关系,有依赖关系的Store必须用上Dispatcher的waitFor桉树
    (2)难以进行服务器端渲染
    (3)Store混杂了逻辑和状态

3.2 Redux(Redux是Flux的一种实现)

基本原则

  1. 唯一数据源:只存储在惟一的一个Store
  2. 保持状态只读:修改状态只能创建一个新的状态对象返回给Redux,由Redux完成新的状态的组装
  3. 数据改变只能通过纯函数完成:纯函数Reducer
    1
    reducer(state,action)

React的Rudex只负责计算状态,不负责存储状态

Redux实例

React和Redux是两个独立的产品,Redux中每个构造函数都返回一个action对象,Flux中的Dispatcher简化为Store上的一个dispatcher,state是由Store管理的

容器组件和傻瓜组件

容器组件:负责和Redux Store打交道,读取、监听store的状态,派发action对象,通过props将状态传递给展示组件
傻瓜组件(展示组件):负责渲染界面的组件,没有state

组件Context “上下文环境”

解决问题:解决需要一层层传递props
解决方法:让一个树状组件所有组件都能访问一个共同的对象,创建特殊React组件(Provider)为Context提供者
Provider的render函数简单的把子组件渲染出来

  1. 每个React组件props都有一个特殊属性children代表子组件
  2. childContextTypes属性,getChildContext函数,两者相互对应,子组件才能访问到context

    1
    2
    3
    Provider.childContextType = {
    store: PropTypes.object
    }
  3. 子组件通过this.context访问context对象

  4. 更新constructor
    1
    super(props,context)

React-Redux

  1. connect连接容器组件和傻瓜组件
    执行两次函数:第一次执行connect函数,第二次执行connect函数返回的函数;最后产生容器组件
    1
    export default connect(mapStateToProps,mapDispatchToProps)(Counter)

connect函数具体工作:把Store上的状态转化为内层傻瓜函数的prop(mapStateToProps);把内层傻瓜组件中的用户动作转化为派送给Store的动作(mapDispatchToProps)

  1. Provider
    store必须包含三个函数的object,即subscribe、dispatch、getState

第四章 模块化React和Redux应用

4.2 代码文件的组织方式

  1. 按角色组织
  2. 按功能组织
    index.js文件作为模块公开接口

4.3 状态树的设计:Store上状态树的设计

  1. 一个状态节点只属于一个模块
    Store上的state只能通过reducer来更改,导出的reducer只能修改Redux的状态树上一个节点下的数据
  2. 避免冗余数据

范式化:传统的关系型数据库对数据结构进行的操作,其实就是去除数据的冗余,对数据结构的处理和关系型数据库正好相反,通过数据冗余来减少读取数据库时的数据关联工作

  1. 树形结构扁平

4.5 ToDo应用实例

  1. Redux的createStore只接受一个reducer,却可以把多个reducer结合起来
  2. render函数对ref的用法
    (1)当一个包含ref属性的组件完成装载过程时,会检测ref是不是一个函数,如果是,就会调用render,参数为这个组件代表的DOM元素render={this.refInput},通过这种方法,我们的代码可以访问到实际的DOM元素
    (2)组件完成mount后,可以直接通过this.input访问input元素,即为一个DOM元素,可以使用DOM API 访问元素内容
    (3)表格提交时,可以通过ev.preventDefault()取消浏览器默认网页跳转行为
    (4)ref实际触及了DOM元素,不推荐使用
    解决方案
    利用组件状态来同步记录DOM元素的值,这种方法可以控制住组件不使用ref
  3. 动态数量组件:使用数组的map属性,子组件需要添加key属性。JSX中可以使用任何形式的js表达式,只要js表达式在符号{}之间,for或者while产生的是”表达式”。JSX最终会被babel转移成一个嵌套的函数调用

第七章 Redux和服务器通信

1.React组件访问服务器

  1. Provider提供包含store的context