github源码:https://github.com/mocheng/react-and-reduxjavascript
import PropTypes from 'prop-types';css
Counter.propTypes = { caption: PropTypes.string.isRequired, initValue: PropTypes.number }
prop Types 虽然可以在开发阶段发现代码中的问题,可是放在产品环境中就不大合
适,现有的 babel-react-optimize 就具备这个功能,能够经过 npm 安装,但
是应该确保只在发布产品代码时使用它。html
props默认值 React的 defaultProps 功能,让代码更加容易读懂
Counter 组件添加 defaultProps 的代码以下:vue
Counter .defaultProps = { initValue: 0 }
1 初始化 state ,由于组件生命周期中任何函数均可能要访问 state ,那么整个生命
周期中第一个被调用的构造函数天然是初始化 state 最理想的地方;
2 绑定成员函数的 this 环境java
ES5的 React. createClass 方法创造的组件类才会发生做用,已经被 Facebook 官方逐渐废弃react
render 函数应该是一个纯函数,彻底根据 this.state this.props 来决定返
回的结果,并且不要产生任何反作用。在 render 函数中去调用 this.setState 毫无疑问是错
误的,由于一个纯函数不该该引发状态的改变ios
1 在装载过程当中, componentWil!Mount 会在调用 render 函数以前被调用, componentDidMount
会在调用 render 函数以后被调用,这两个函数就像是 render 函数的前哨和后
卫,一前一后,把 render 函数夹住,正好分别作 render 先后必要的工做git
2 componentWillMount 都是紧贴着本身组件的 render 函数之
前被调用, componentDidMount 可不是紧跟着 render 函数被调用,当全部三个组件的
render 函数都被调用以后, 个组件的 componentDidMount 才连在一块儿被调用
之因此会有上面的现象,是由于 render 函数自己并不往 DOM 树上渲染或者装载内
容,它只是返回 JSX 表示的对象,而后由 React 库来根据返回对象决定如何渲染
React 库确定是要把全部组件返回的结果综合起来,才能知道该如何产生对应的 DOM
修改 因此,只有 React 库调用 Counter 组件的 render 函数以后,才有可能完成装
载,这时候才会依次调用各个组件的 componentDidMount 函数做为装载过程的收尾github
3 componentWilIMount componentDidMount 这对兄弟函数还有一个区别,就是 componentWillMount
能够在服务器端被调用,也能够在浏览器端被调用;而 component-DidMount
只能在浏览器端被调用,在服务器端使用 React 的时候不会被调用ajax
1.只要是父组件的 render 函数被调用,在 render 函数里面被谊染的子组件就会经历更新过
程,无论父组件传给子组件的 props 有没有改变,都会触发子组件的 componentWillReceiveProps
函数
二、注意,经过 this.setState 方法触发的更新过程不会调用这个函数,这是由于这个函数
适合根据新的 props 值(也就是参数 nextProps )来计算出是否是要更新内部状态 state
更新组件内部状态的方法就是 this.setState ,若是 this.setState 的调用致使 componentWillReceiveProps再一次被调用,那就是一个死循环了
三、this.setState 不会引起这个函数 componentWillReceiveProps
被调用
四、在 React 的组件组合中,彻底能够只渲染 个子组件,
而其余组件彻底不须要渲染,这是提升 React 性能的重要方式
一、render 函数重要,是由于 render 函数决定了该渲染什么,而说 shouldComponentUpdate
函数重要,是由于它决定了一个组件何时不须要渲染
二、说shouldComponentUpdate 重要,就是由于只要使用恰当,他就可以大大提升 React
组件的性能,虽然 React 的渲染性能已经很不错了,可是,无论渲染有多快,若是发现
不必从新渲染,那就干脆不用渲染好了,速度会更快
shouldComponentUpdate(nextProps, nextState) { return (nextProps.caption !== this.props.caption) || (nextState.count !== this.state.count); }
如今,只有当 caption 改变,或者 state 中的 count 值改变, shouldComponent 才会返回
true
3.经过 this setState 函数引起更新过程,并非马上更新组件的 state
值,在执行到到函数 shouldComponentUpdate 的时候, this state 依然是 this.setState 函数
执行以前的值,因此咱们要作的实际上就是在 nextProps nextState this.props this.state 中互相比对
1.若是组件的 shouldComponentUpdate 函数返回 true
二、当在服务器端使用 React 渲染时,这一对函数中的 Did 函数,
也就是 componentDidUpdate 函数,并非只在浏览器端才执行的,不管更新过程发生在
服务器端仍是浏览器端,该函数都会被调用
3.React 组件被更新时,原有的内容被从新绘
制,这时候就须要在 componentDidUpdate 函数再次调用 jQuery 代码
4.读者可能会问, componentDidUpdate 函数不是可能会在服务器端也被执行吗?在
服务器端怎么可以使用 jQuery 呢?实际上,使用 React 作服务器端渲染时,基本不会经
历更新过程,由于服务器端只须要产出 HTML 字符串,一个装载过程就足够产出 HTML
了,因此正常状况下服务器端不会调用 componentDidUpdate 函数,若是调用了,说明我
们的程序有错误,须要改进
一、React 组件要从
DOM 树上删除掉以前,对应的 componentWillUnmount 函数会被调用,因此这个函数适
合作一些清理性的工做
二、不过, componentWillUnmount 中的工做每每和 componentDidMount 有关,好比,在
componentDidMount 中用非 React 的方法创造了一些 DOM 元素,若是撒手无论可能会造
成内存泄露,那就须要在 componentWillUnmount 中把这些创造的 DOM 元素清理掉
Counter.propTypes = { caption: PropTypes.string.isRequired, initValue: PropTypes.number, onUpdate: PropTypes.func }; Counter.defaultProps = { initValue: 0, onUpdate: f => f //默认这个函数什么也不作 };
新增长的 prop 叫作 onUpdate ,类型是一个函数,当 Counter 的状态改变的时候,就
会调用这个给定的函数,从而达到通知父组件的做用
这样, Counter的 onUpdate 就成了做为子组件的 Counter 向父组件 ControlPanel
递数据的渠道,咱们先约定这个函数的第一个参数是 Counter 更新以后的新值,第二个
参数是更新以前的值,至于如何使用这两个参数的值,是父组件 ControlPanel 的逻辑,
Counter 不用操心,并且根据两个参数的值足够能够推导出数值是增长仍是减小
1.Flux 的体系中,若是两个 Store 之间有逻辑依赖关系,就必须用上 Dispatcher的
waitFor 函数 在上面的例子中咱们已经使用过 waitFor 函数, SummaryStore对 action 类型的
处理,依赖于 CounterStore 已经处理过了 因此,必需要经过 waitFor 函数告诉 Dispatcher,
先让 CounterStore 处理这些 action 对象,只有 CounterStore 搞定以后 SummaryStore才
继续
2.那么, SummaryStore 如何标识 CounterStore 呢?靠的是 register 函数的返回值 dispatchToken
,而 dispatchToken 的产生,固然是 CounterStore 控制的,换句话说,要这样设计:
1)CounterStore 必需要把注册回调函数时产生的 dispatchToken 公之于众;
2)SummaryStore 必需要在代码里创建对 CounterStore的 dispatchToken 的依赖
虽然 Flux 这个设计的确解决了 Store 之间的依赖关系,可是,这样明显的模块之间
的依赖,看着仍是让人感受不大舒服,毕竟,最好的依赖管理是根本不让依赖产生
1).若是状态数据分散在多个 Store 中,容易形成数据冗余,这样数据一致性方面就会出
问题。 虽然利用 Dispatcher的 waitFor 方法能够保证多个 Store 之间的更新顺序,可是这
又产生了不一样 Store 之间的显示依赖关系,这种依赖关系的存在增长了应用的复杂度,容
易带来新的问题
Redux 对这个问题的解决方法就是,整个应用只保持一个 Store ,全部组件的数据源
就是这个 Store 上的状态
2)Redux阻并无阻止一个应用拥有多个Store,只是,在Redux的框架下,让一个应
用拥有多个 Store 不会带来任何好处,最后还不如使用一个 Store 更容易组织代码
修改 Store 的状态,必需要经过派发一个
action 对象完成,这一点 ,和 Flux 的要求并无什么区别
这里所说的纯函数就是 Reducer
在 Redux 中,一个实现一样功能的 reducer 代码
以下:
function reducer(state , action) => { const {counterCaption} = action; switch (act on.type) { case ActionTypes.INCREMENT : return { ... state , [ counterCaption] : state [ counterCaption ] + 1}; case ActionTypes . DECREMENT: return { ... state, [counterCaption] : state [ counterCaption) - 1}; default : return state } }
能够看到 reducer 函数不光接受 action 为参数,还接受 state 为参数 也就是说, Redux的
reducer 只负责计算状态,却并不负责存储状态
“若是你愿意限制作事方式的灵活度,你几乎总会发现能够作得更好。”
一一-John earmark
做为制做出《 Doom >< Quake 》这样游戏的杰出开发者, John earmark 这句话道出
了软件开发中的一个真谛
在计算机编程的世界里,完成任何一件任务,可能都有一百种以上的方法,可是无节制的灵活度反而让软件难以维护增长限制是提升软件质量的法门。
创造一个 src/Store 文件,这个文件输出全局惟一的那个 Store
import {createStore} from 'redux'; import reducer from './Reducer.js'; const initValues = { 'First': 0, 'Second': 10, 'Third': 20 }; const store = createStore(reducer, initValues);
在这里,咱们接触到了 Redux 库提供的 create Store 函数,这个函数第一个参数表明更
新状态的 reducer ,第二个参数是状态的初始值,第三个参数可选,表明 Store Enhancer,
在这个简单例子中用不上,在后面的章节中会详细介绍
reducer文件
import * as ActionTypes from './ActionTypes'; export default (state,action)=>{ const {counterCaption} = action; switch (action.type){ case ActionTypes.INCREMENT: return { ...state, [counterCaption]:state[counterCaption]+1 } case ActionTypes.DECREMENT: return { ...state, [counterCaption]:state[counterCaption]-1 } default: return state } };
扩展操做符 (spread operator) 并非因桦的一部分,甚至都不是 ES Next 刁飞 法的一部分,可是
由于其语法简单,已经被普遍使用,由于 babel 的存在,也不 会有兼容性问题,因此咱们彻底能够放心使用
1.傻瓜组件 Counter 代码的逻辑史无前例的简单,只有一个 render 函数
1.CounterContainer ,这是容器组件,组件承担了全部的和 Store 关联的工做,它的 render 函数所作的就是渲染傻瓜组件 Counter 而已,只负责传递必要的 prop
1.Provider 也是一个 React 组件,不过它的 render 函数就是简单地把子组件渲染出来,
在渲染上, Provider 不作任何附加的事情
import {PropTypes, Component} from 'react'; class Provider extends Component { getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } Provider.propTypes = { store: PropTypes.object.isRequired } Provider.childContextTypes = { store: PropTypes.object }; export default Provider;
<Provider store={store}> <ControlPanel /> </Provider>,
以Counter 组件为例,react-redux 的例子中没
有定义 CounterContainer 这样命名的容器组件,而是直接导出了一个这样的语句
export default connect(mapStateToProps, mapDispatchToProps), Counter);
1)第一眼看去,会让人以为这不是正常的 JavaScript 语法 其实, connect是 react-redux
提供的一个方法,这个方法接收两个参数 mapStateToProps和 mapDispatch-ToProps ,执行
结果依然是一个函数,因此才能够在后面又加一个圆括号,把 connect 函数执行的结果立
刻执行,这一次参数是 Counter 这个傻瓜组件。
2)这里有两次函数执行,第一次是 connect 函数的执行,第二次是把 connect 函数返回
的函数再次执行,最后产生的就是容器组件,功能至关于前面 redux_smart_dumb 中的
CounterContainer;
3)这个 connect 函数具体作了什么工做呢?
4)mapStateToProps函数
function mapStateToProps(state ,ownProps) { return { value: state[ownProps.caption] } }
5)mapDispatchToProps函数
function mapDispatchToProps(dispatch, ownProps) { return { nincrement () => { dispatch(Actions.increment(ownProps.caption)); } onDecrement : () => { dispatch(Actions.decrement(ownProps.caption)); } } }
6)mapStateToProps和 mapDispatchToProps 均可以包含第二个参数,表明 ownProps,
也就是直接传递给外层容器组件的 props ,在 ControlPanel 的例子中没有用到,咱们在后
续章节中会有详细介绍
react-redux 和咱们例子中的 Provider 几乎同样,可是更加严谨,好比咱们只要求 store 属性是一个 object ,而react-redux 要求 store 不光是 object ,并且是必须包含三个函数的 object ,这三个函数
分别是
拥有上述 3个函数的对象,才能称之为一个 Redux 的store;
另外, react-redux 定义了 Provider的 componentWillReceiveProps 函数,在 React组
件的生命周期中, componentWillReceiveProps 函数在每次从新渲染时都会调用到, react-redux在
componentWillReceiveProps 函数中会检查这一次渲染时表明 store的 prop 和上
一次的是否同样。 若是不同,就会给出警告,这样作是为了不屡次渲染用了不一样的
Redux Store。 每一个 Redux 应用只能有一个 Redux Store ,在整个 Redux 的生命周期中都
应该保持 Store 的惟一性
reducers/ todoReducer. js filterReducer.js actions/ todoActions.js filterActions.js components/ doList js todoitern . js filter.js containers/ todoListContainer . js todoiternCont ainer . js filterContaine r. js
“在最理想的状况下,咱们应该经过增长代码就能增长系统的功能,而不是 经过对现有代码的修改来增长功能"一一-Robert C. Martin
好比,若是 模块的 reducer 负责修改状态树上 字段下的数据,那么另 个模块
reducer 就不可能有机会修改 字段下的数据
工欲善其事,必先利其器 一一《论语·卫灵公》
举个例子 在更新以前,组件的结构是这样:
<div> <Todos /> </div>
咱们想要更新成这样:
<span> <Todos /> </span>
这时候, componentWillUnmount 方法会被调用,取而代之的组件则会经历装载过程
的生命周期,组件的 componentWillMount render componentDidMount 方法依次被
调用,一看根节点原来是 div ,新的根节点是 span ,类型就不同,切推倒重
来。
虽然是浪费,可是为了不 O(N3)的时间复杂度, React 必需要选择 个更简单更快
捷的算法,也就只能采用这种方式
**做为开发者,很显然必定要避免上面这样浪费的情景出现 因此, 必定要避免做为
包裹功能的节点类型被随意改变**
好比本来的节点用 JSX 表示是这样:
<div style={{color :”red”, fontSize: 15}} className=” welcome ” > Hello World </div>
改变以后的 JSX 表示是这样:
<div style={{color :” gree ”, fontSize: 15}} className=” farewell ” > Good Bye </div>
React 能作的只是根据新节点的 props 去更新原来根节点的组件实例,
引起这个组件实例的更新过程,也就是按照顺序引起下列函数:
若是 shouldComponentUpdate 函数返回 false 的话,那么更新过程
就此打住,再也不继续 因此为了保持最大的性能,每一个 React 组件类必需要重视 shouldComponentUpdate
,若是发现根本没有必要从新渲染,那就能够直接返回 false
<ul> <Todoitem text=” First” completed={ false}> <To do tern text Second completed={false}> </ul>
在更新以后,用 JSX 表示是这样:
<ul> <Todo item text=” First ” completed={ false}> <Todoitem text=” Second" completed={false}> <Todo tem text=”Third” completed={false}> </ul>
那么 React 会发现多出了一个 Todoltem ,会建立一个新的 Todoltem 组件实例,这个
Todoltem 组件实例须要经历装载过程,对于前两个 Todoltem 实例, React 会引起它们的
更新过程,可是只要 To do Item shouldComponentUpdate 函数实现恰当,检查 props
后就返回 false 的话,就能够避免实质的更新操做
<ul> <Todoltem text=” Zero” completed={ false)> <Todoltem text=”First” completed={ false)> <Todoltem text=”Second” completed={ false)> </ul>
从直观上看,内容是“ Zero ,,的新加待办事项被插在了第一位,只须要创造一个新
的组件 Todoltem 实例放在第一位,剩下两个内容为“ First ”和“ Second ,,的 Todoltem
实例经历更新过程,可是由于 props 没有改变,因此 shouldComponentUpdate 能够帮助
这两个组件不作实质的更新动做。
但是实际状况并非这样 若是要让 React 按照上面咱们构想的方式来作,就必须
要找出两个子组件序列的不一样之处,现有的计算出两个序列差别的算法时间是 O(N2),虽
然没有树形结构比较的 O(N3)时间复杂度那么夸张,可是也不适合一个对性能要求很高
的场景,因此 React 选择看起来很傻的一个办法,不是寻找两个序列的精确差异,而是
直接挨个比较每一个子组件。
, React 并非没有意识到这个问题,因此 React 提供了方法来克服这种浪费,
不过须要开发人员在写代码的时候提供一点小小的帮助,这就是 key 的做用
用数组下标做为 key ,看起来 key 值是惟一的,可是却不是稳定不变的,随着 todos
数组值的不一样,一样一个 Todoltem 实例在不一样的更新过程当中在数组中的下标彻底可能不
同,把下标当作 key 就让 React 完全乱套了。
须要注意,虽然 key 是一个 prop ,可是接受 key 的组件并不能读取到 key 的值,因
key 和ref是 React 保留的两个特殊 prop ,并无预期让组件直接访问。
const selectVisibleTodos = (todos, filter) => { switch (filter) { case FilterTypes.ALL: return todos; case FilterTypes.COMPLETED: return todos.filter(item => item.completed); case FilterTypes.UNCOMPLETED: return todos.filter(item => !item.completed); default: throw new Error('unsupported filter'); } } const mapStateToProps = (state) => { return { todos: selectVisibleTodos(state.todos, state.filter) }; }
既然这个 selectVisibleTodos 函数的计算必不可少,那如何优化呢?
若是上 次的计算结果被缓存起来的话,那就能够重用缓存的
数据。
这就是 reselect 库的工做原理:只要相关状态没有改变,那就直接使用上一次的缓存
结果
npm install --save reselect import {createSelector} from ’ reselect ’; import {FilterTypes} from ’.. /constants. j s ’; export const selectVisibleTodos = createSelector( [getFilter, getTodos], (filter, todos) => { switch (filter) { case FilterTypes.ALL: return todos; case FilterTypes.COMPLETED: return todos.filter(item =>item.completed}; case FilterTypes.UNCOMPPLETED: return todos.filter(item => !item.completed); default: throw new Error (’ unsupported filter ’); } } )
reselect 提供了创造选择器的 createSelector 函数 ,这是一个高阶函数,也就是接受
函数为参数来产生一个新函数的函数
第一个参数是一个函数数组,每一个元素表明了选择器步骤一须要作的映射计算,这
里咱们提供了两个函数 getFilte和 getTodos ,对应代码以下:
const getFilter = (state) => state.filter; const getTodos = (state) => state.todos;
所谓范式化,就是遵守关系型数据库的设计原则,减小冗余数据.
若是使用反范式化的设计,那么状态树上的数据最好是可以不用计算拿来就能用,
在Redux Store 状态树的 todos 字段保存的是全部待办事项数据的数组,对于每一个数组元
素,反范式化的设计会是相似下面的对象:
{ id: 1, //待办事项id text :”待办事项 ”,//待办事项文字内容 completed : false,//是否已完成 type: { //种类 name :”紧急”,//种类的名称 color:”red” //种类的显示颜色 } }
但这也有缺点,当须要改变某种类型的名称和颜色时,
不得不遍历全部 Todoltem 数据来完成改变.
反范式化数据结构的特色就是读取容易,修改比较麻烦
若是使用范式化的数据结构设计,那么 Redux Store 上表明 Todoltem 的一条数据是
相似下面的对象:
{ id: 1 , text :”待办事项 l ”, completed : false , typeid: 1 //待办事项所属的种类id }
用一个typeId 表明类型,而后在 Redux Store 上和 to dos 平级的根节点位置建立一个
types 字段,内容是一 个数组,每一个数组元素表明 一个类型,一个种类的数据是相似下面
的对象:
{ id: 1 , //种类 name :” 紧急 ”, //种类的名称 color :”red” //种类的显示颜 }
当Todoltem 组件要渲染内容时从 Redux Store 状态树的 to dos 宇段下获取的数据
是不够的,由于只有 typeId。
这个过程固然要花费一 些时间,可是当要改变某个种类的名称或者颜色时,就异常
地简单,只须要修改 types 中的一处数据就能够了
1.利用 react-redux 提供的 shouldComponentUpdate 实现来提升
组件渲染功能的方法, 一个要诀就是避免传递给其余组件的 prop 值是 一个不一样的对象,
否则会形成元谓的重复渲染
2.不能随意修改一个做为容器的 HTML 节点的类型 其次,对于动态数
量的同类型子组件,一 定要使用 key 这个 prop
3.利用 reselect 库来实现高效的数据获取。 由于 reselect 的缓存功
能,开发者不用顾忌范式化的状态树会存在性能问题, Redux Store 的状态树应该按照范
式化原则来设计,减小数据冗余,这样利于保持数据一致。
“重复是优秀系统设计的大敌。 ”一-Robert C.Martin
1.高阶组件( Higher Order Component, HOC )并非 React 提供的某种 API ,而是使用
React 的一种模式,用于加强现有组件的功能。
2.简单来讲,一个高阶组件就是一个函数,这个函数接受一个组件做为输入,而后返回一个新的组件做为结果,并且,返回的新组件拥有了输入组件所不具备的功能
3.这里提到的组件指的并非组件实例,而是一个组件类,也能够是一个无状态组件
的函数。
4.咱们先看一个很是简单的高阶组件的例子,感觉一下高阶组件是如何工做的,代码
以下:
import React from ’ react ’; function removeUserProp(WrappedComponent) { return class WrappingComponent extends React.Component { render() { const {user, ... otherProps} = this.props; return <WrappedComponent { ... otherProps) /> } } } export default removeUserProp;
只是忽略名为 user的 prop 也就是说,若是 Wrapped Component 可以处理名为 user的
prop ,这个高阶组件返回的组件则彻底无视这个 prop。
5.假如咱们如今不但愿某个组件接收到 user prop ,那么咱们就不要直接使用这个组
件,而是把这个组件做为参数传递给 removeU serProp 函数,而后咱们把这个函数的返回
结果当作组件来使用:
const NewComponent = removeUserProp(SampleComponent) ;
在上面的代码中, NewComponent 拥有和 SampleComponent 彻底同样的行为,惟一
的区别就是即便传递 user 属性给它,它也会当没有 user 来处理。
6.定义高阶组件的意义何在呢?
1.上面的 removeUserProp 例子就是一个代理方式的高阶组件,特色是返回的新组件类
直接继承自 React. Component 新组件扮演的角色是传入参数组件的一个“代理”,在
新组建的 render 函数中,把被包裹组件渲染出来,除了高阶组件本身要作的工做,其他
功能全都转手给了被包裹的组件.
2.若是高阶组件要作的功能不涉及除了 render 以外的生命周期函数,也不须要维护自
己的状态,那也能够干脆返回一个纯函数,像上面的 removeUserProp ,代码能够简写成
下面这样:
function removeUserProp(WrappedComponent) { return function newRender(props) { con st {user, ... otherProps) = props; return <WrappedComponent { ... otherProps} /> } }
3.代理方式的高阶组件,能够应用在下列场景中:
例如,咱们能够定义一个高阶组件,让参数组件只有在用户登陆时才显示,代码
以下:
const onlyForLoggedinHOC = (WrappedComponent) => { return class NewComponent extends WrappedComponent { render () { if (this.props.loggedin) { return super.render(); } else { return null; } } } }
又例如,咱们能够从新定义 shouldComponentUpdate 函数,只要 prop 中的 useCache
不为逻辑 false 就不作从新渲染的动做,代码以下:
const cacheHOC = (WrappedComponent) => { return class NewComponent extends WrappedComponent { shouldComponentUpdate(nextProps, nextState) { return !nextProps.useCache; } } }
在 ES6的 React组件类定义方法中不能使用 Mixin, React 官方也很明确声明 Mixin 是应该被废弃的方法
因此咱们只须要知道在 React 的历史上,曾经有这样一个重用代码的解决方法就足够了
使用 Redux 访问服务器,一样要解决的是异步问题
Redux 的单向数据流是同步操做,驱动 Redux 流程的 ac tion 对象, 每个 action
对象被派发到 Store 上以后,同步地被分配给全部的 reducer 函数,每一个 reducer 都是纯
函数,纯函数不产生任何反作用,天然是完成数据操做以后马上同步返回, reducer 返回
的结果又被同步地拿去更新 Store 上的状态数据,更新状态数据的操做会马上被同步给监
Store 状态改变的函数,从而引起做为视图的 React 组件更新过程。
实际上, re dux-thunk 的实现极其简单,只有几行代码。
假若有一个 JavaScript 函数f 以下定义:
const f = (x) => { return x () + 5; }
f把输入参数x 当作一个子程序来执行,结果加上5 就是f 的执行结果,那么咱们试
着调用一次 f:
const g = () => { return 3 + 4 ; } f (g); 11 结果是( 3+4 )巧= 37
上面代码中函数f 就是一个 thunk ,这样使用看起来有点奇怪,但有个好处就是g的
执行只有在f 实际执行时才执行,能够起到延迟执行的做用,咱们继续看 redux-thunk的
用法来理解其意义。
按照 redux-thunk 的想法,在 Redux 的单向数据流中,在 action 对象被 reducer 函数
处理以前,是插入异步功能的时机
在Redux 架构下,一个 action 对象在经过 store.dispatch派发,在调用 reducer 函数
以前,会先通过 个中间件的环节,这就是产生异步操做的机会,实际上 redux-thunk提
供的就是一个Redux 中间件,咱们须要在建立 Store 时用上这个中间件。
redux-也unk 的工做是检查 action 对象是否是函数,若是不是函数就放行,完成普通
action 对象的生命周期,而若是发现 action 对象是函数,那就执行这个函数,并把 Store的
dispatch 函数和 getState 函数做为参数传递到函数中去,处理过程到此为止,不会让
这个异步 action 对象继续往前派发到 reducer 函数
举一个并不涉及网络 API 访问的异步操做例子 ,在 Co unter 组件中存在一个普通的
同步增长计数的 action 构造函数 increment ,代码以下:
const increment= () => ({ type: ActionTypes.INCREMENT, });
派发 increment 执行返回的 action 对象, Redux 会同步更新 Store 状态和视图,可是
咱们如今想要创造一个功能,可以发出一个“让 Counter 组件在 秒以后计数加一”的
指令,这就须要定义一个新的异步 action 构造函数,代码以下:
const incrementAsync = () => { return (dispatch) => { set Timeout ( () => { dispatch (increment()); },1000); } }
异步 action 构造函数 incrementAsync 返回的是一个新的函数,这样一 个函数被
dispatch 函数派发以后,会被 redux-thunk 中间件执行,因而 setTimeout 函数就会发生做
用,在 1秒以后利用参数 dispatch 函数派发出同步 action 构造函数 increment 的结果。
这就是异步 action 的工做机理,这个例子虽然简单,可是能够看得出来,异步
action 最终仍是要产生同步 action 派发才能对 Redux 系统产生影响。
对于访问服务器这样的异步操做,从发起操做到操做结束,都会有段时间延迟,在
这段延迟时间中,用户可能但愿停止异步操做。
用户也会进行一些操做引起新的请求发往服务器,而这就是咱们开发者须要考虑的问题。
从用户角度出发但愿是最后一次选择结果。
在jQuery 中,能够经过 abort 方法取消掉一个 AJAX 请求:
const xhr = $.ajax( ... ); xhr.abort {);//取消掉已经发出的AJAX请求
可是,很不幸,对于 fetch 没有对应 abort 函数的功能,由于 fetch 返回的是一个
Promise 对象,在 ES6 的标准中, Promise 对象是不存在“中断”这样的概念的.
既然 fetch 不能帮助咱们停止一个 API 请求,那就只能在应用层实现“中断”的效
果,有一个技巧能够解决这个问题,只须要修改 action 构造函数。
let nextSeqid = 0; export const fetchWeather = (cityCode) => { return (dispatch) => { const apiUrl =、/ data/cityinfo/${cityCode).html const seqid = ++ nextSeqid; const dispatchifValid = (action) => { if (seqid === nextSeqid) { //**这里一个请求对应一个请求`id`若是不相等,就抛弃** return dispatch(action); } } dispatchifValid ( fetchWeatherStarted () ) fetch(apiUrl) .then((response) => { if (response.status !== 200) { throw new Erro r (’ Fail to get response with status ’+ response.status); } response.json() .then((responseJson) => { dispatchifValid(fetchWeatherSuccess(responseJson.weatherinfo)); )).catch((error) => { dispatchifVal (fetchWeatherFailure(error)); )); }).catch ((error) => { dispatchifValid(fetchWeatherFailure(error)); }) } }
在action 构造函数文件中定义一个文件模块级的 nextSeqld 变量,这是一个递增的整
数数字,给每个访问 API 的请求作序列编号。
这里一个请求对应一个请求id
若是不相等,就抛弃。
若是还不明白,另外用vue的例子来讲明:
<div id="app"> <select @change="_getWeather"> <option v-for="(value, key) in city_code" >{{key}}</option> </select> <div>{{cont}}</div> </div>
<script> new Vue({ el:'#app', data:{ nextSeqid:0, baseUrl:'http://www.weather.com.cn', city_code: { '北京': 101010100, '上海': 101020100, '广州': 101280101, '深圳': 101280601 }, cont:'正在请求。。。' }, methods:{ _getWeather(e){ const seqid = ++ this.nextSeqid; console.log(seqid,this.nextSeqid,'请求') let url=`/data/cityinfo/${this.city_code[e.target.value]}.html`; axios.get(url).then(res=>{ let {city}=res.data.weatherinfo; console.log(seqid,this.nextSeqid,'请求完成') if (seqid === this.nextSeqid) { this.cont=city; } }) } } }) </script>
控制台结果:
- 1 1 "请求" - (index):55 2 2 "请求" - (index):60 1 2 "请求完成" - (index):55 3 3 "请求" - (index):60 2 3 "请求完成" - (index):55 4 4 "请求" - (index):60 3 4 "请求完成" - (index):55 5 5 "请求" - (index):60 4 5 "请求完成" - (index):55 6 6 "请求" - (index):60 5 6 "请求完成" - (index):55 7 7 "请求" - (index):60 6 7 "请求完成" - (index):60 7 7 "请求完成"
你会发如今重复请求,请求id不对应,因此不渲染,只有当相等菜渲染。
if (seqid === this.nextSeqid) { this.cont=city; }
虽然不能真正“停止”一个 API 请求,可是咱们能够用这种方法让一个 API 请求的
结果被忽略,达到了停止一个 API 请求同样的效果。
在这个例子中 Weather 模块只有一种API 请求,因此一个 API 调用编号序列就足够,
若是须要多种 API 请求,则须要更多相似nextSeqld 的变量来存储调用编号。
运行效率要比脚本方式高,由于浏览器原生支持,省去了 Java
Script 的解释执行负担,有的浏览器(好比 Chrome 浏览器)甚至还能够充分利用 GPU加
速的优点,进一步加强了动画渲染的性能
时间和速度曲线的不合理是 CSS3 先天的属性更让开发者头疼的就是开发 CSS3
则的过程,尤为是对 tra nsition-duration 时间很短的动画调试,由于 CSS3 transition
程老是一闪而过,捕捉不到中间状态,只能一遍一遍用肉眼去检验动画效果,用 CSS3
作过复杂动画的开发者确定都深有体会
虽然 CSS3 有这样一些缺点,可是由于其无与伦比的性能,用来处理一些简单的动
画仍是不错的选择
React 提供的 ReactCSSTransitionGroup 功能,使用的就是 CSS3 的方式来实现动画,
在后面的章节会详细介绍
脚本方式最大的好处就是更强的灵活度,最原始的脚本方式就是利用 setlnterval 或者 setTimeout 来实现。
var animatedElement = document.getElementById ('sample'); var left = 0; var timer; var ANIMATIONINTERVAL = 16; timer = setInterval (function() { left += 10; animatedElement.style.left = left + 'px'; if ( left >= 400 ) { clearInterval(timer); } } , ANIMATIONINTERVAL);
在上面的例子中,有一个常量 ANIMATION INTERVAL 定义为 16 , setlnterval 以这
个常盘为间隔,每 16 毫秒计算一次 sample 元素的 left 值,每次都根据时间推移按比例增长 left 的值,直到 left 大于 400.
为何要选择 16 毫秒呢?由于每秒渲染 60 帧(也叫 60fps, 60 Frame Per Second)
会给用户带来足够流畅的视觉体验,一秒钟有 1000 毫秒, 1000/60约等于16 ,也就是说,如
果咱们作到每 16 毫秒去渲染一次画面,就可以达到比较流畅的动画效果。
对于简单的动画, setlnterval 方式勉强可以及格,可是对于稍微复杂一些的动画,脚
本方式就顶不住了,好比渲染一帧要花去超过 32 毫秒的时间,那么还用 16 毫秒一个间
隔的方式确定不行 实际上,由于一帧渲染要占用网页线程 32 毫秒,会致使 setlnterval
根本没法以 16 毫秒间隔调用渲染函数,这就产生了明显的动画滞后感,本来一秒钟完
成的动画如今要花两秒钟完成,因此这种原始的 setlnterval 方式是确定不适合复杂的动
画的。
出现上面问题的本质缘由是 setlnterval和setTimeout 并不能保证在指定时间间隔或
者延迟的状况下准时调用指定函数 因此能够换 个思路,当指定函数调用的时候,根
据逝去的时间计算当前这一帧应该显示成什么样子,这样即便由于浏览器渲染主线程忙
碌致使一帧渲染时间超过 16 毫秒,在后续帧谊染时至少内容不会所以滞后,即便达不倒
60fps 的效果,也能保证动画在指定时间内完成。
下面是一个这种方法实现动画的例子,首先咱们实现一个 raf 函数, raf request
animation frame 的缩写,代码以下:
var lastTmeStamp = new Date().getTime(); function raf(fn) { var currTimeStamp = new Date().getTime(); var delay = Math.max(O, 16 - (currTimeStamp - lastTmeStamp)); var handle = setTimeout(function(){ fn(currTimeStamp) },delay); lastTmeStamp = currTimeStamp; return handle; }
在上面定义的 raf 中,接受的 fn 函数参数是真正的渲染过程, raf 只是协调渲染的节奏。
raf 尽可能以每隔 16 毫秒的速度去调用传染的fn参数,若是发现上一次被调用时间和
这一次被调用时间相差不足 16 毫秒,就会保持 16 毫秒一次的渲染间隔继续,若是发现
两次调用时间间隔已经超出了 16 毫秒,就会在下 次时钟周期马上调用 fn。
仍是让 id 为sample 的元素向右移动的例子,咱们定义渲染每一帧的函数 render ,代
码以下:
var left = 0; var animatedElement = document.getElementById("sample"); var startTimestamp = new Date().getTime(); function render(timestamp) { left += (timestamp - startTimestamp) / 16; animatedElement.style.left = left + 'px'; if (left < 400) { raf(render); } } raf(render);
上面的 render 函数中根据当前时间和开始动圆的时间差来计算 sample 元素的 left属
性,这样不管 render 函数什么时候被调用,总可以渲染出正确的结果。
最后,咱们将 render 做为参数传递给 raf ,启动了动画过程:
raf (render);
实际上, 现代浏览器提供了 一个新 的函数 requestAnimationFrame ,采用的就是
上面描述的思路,不是以固定 16 毫秒间隔的时间去调用渲染过程,而是让脚本经过
requestAnimationFrame 传一 个回调函数,表示想要渲染一帧画面,浏览器会决定在合
适的时间来调用给定的回调函数,而回调函数的工做是要根据逝去的时间来决定将界面
渲染成什么样子。
这样一来,渲染动面的方式就改为按须要来渲染,而不是每隔 16 毫秒渲染固定的帧内容。
不是全部浏览器都支持 requestAnimationFrame ,对于不支持这个函数的浏览器,可
以使用上面 raf 函数的方式模拟 requestAnimationFrame 的行为。
React 提供了一个叫作 ReactCSSTransitionGroup 的功能帮助实现动画,为了使用这
个功能 ,首先要经过 npm 安装 react-addons-css-transition-group 这个库,以后就能够导人
这个库的内容:
import TransitionGroup from ’ react-addons- css-transition-group ’;
Transition Group 的工做就是帮助组件实现装载过程和卸载过程的动画,而对于更新
过程,并非 Transition Group 要解决的问题.
<ul> <TransitionGroup transitionName="fade" transitionEnterTimeout={500} transitionLeaveTimeout={200}> { todos.map((item) => ( <TodoItem key={item.id} id={item.id} text={item.text} completed={item.completed} /> )) } </TransitionGroup> </ul>
.fade-enter{ opacity: 0.01; } .fade-enter.fade-enter-active { opacity: 1; transition: opacity 500ms ease-in; } .fade-leave { opacity: 1; } .fade-leave.fade-leave-active { opacity: 0.01; transition: opacity 200ms ease-in; }
假设 transitionName sample ,那么定制相关 React 组件的类名就是:
装载时机
读者可能会有一个疑问,为何用 TransitionGroup在 todoList.js 文件中包住全部
Todoltem 组件实例的数组,而不是让 TransitionGroup在 todoltem.js 文件中包住单个
Todoltem 组件呢?
看起来应该能实现一样效果,但实际上这样作不行 由于 TransitionGroup 要发挥做
用,必须自身已经完成装载了 这很好理解, Transition Group 也只是一个 React 组件,
功能只有在被装载以后才能发挥,它本身都没有被装载,怎么可能发挥效力呢?
react-motion 是很优秀的动画库,它采用的动
画方式和 TransitionGroup 不一样,是用脚本的方式。
在这一章中,咱们了解了网页动画的两种实现方式, CSS3 方式和脚本方式,在 React
的世界,也有对应这两种方式的动画解决方案。
React 官方的 ReactCSSTransitionGroup ,可以帮助定制组件在装载过程和卸载过程
中的动画,对于更新过程的动画,则不在 ReactCSSTransitionGroup 考虑之列,能够直接用
CSS3 来实现。
React-Motion 库提供了更强大灵活的动画实现功能,利用“以函数为子组件”的模
式, React-Motion 只须要提供几个组件,这些组件经过定时向子组件提供动画参数,就
可让开发者自由定义动画的功能。
React Redux 都是彻底在浏览器中运行的,其实, React做为一
个产生用户界面的 JavaScript 库, Redux 做为一个管理应用数据的框架,二者也能够
在服务器端运行。
理想状况下, 一个React 组件或者说功能组件既可以在浏览器端渲染也能够在服务
器端渲染产生 HTML ,这种方式叫作“同构”( Isomorphic ),也就是同 份代码能够在不
同环境下运行。
传统的模板库就是生硬的字符串替换操做,不管如何优化都会有它的极限,并且模
板的输出依然是字符串,将 HTML 字符串插入网页的过程,也就是 DOM 树的操做,性
能也没法优化 在前面的章节中咱们介绍过 React的 Virtual DOM 工做原理,配合生命
周期函数的应用,性能不是字符串替换的模板库可以比拟的。
虽然 Face book 声称 React 并非给服务器端渲染设计的,可是 React 真的很适合来作同构