react学习总结(持续更新中......)
一、Router
1.给属性history赋值,常用有两种选择:browserhistory和hashhistory。
这里给出两个例子:

2.设置路由:
2.1给属性routes赋一个对象,在这个对象中指定component和childRoutes,如下图:

2.2 使用子组件Route,然后给Route的属性path和component赋值。

3.设置钩子
每个路由都有Enter和Leave钩子,用户进入或离开该路由时触发。


二、组件生成方法
1.React.createClass

2.继承React.Component

3.通过text/babel和ReactDOM.render的方法生成:

4.使用ReactDOM.render和Route,参考上一节:

5.傻瓜式组件或纯展示组件的几种简约写法
傻瓜组件只有一个render函数,如下:
class Counter extends Component {
render() {
return (
<div> ...</div>
);
}
}
假如由props传入Counter:
function Counter(props) {
const {A, B, C} = props;
return (
<div> ...</div>
);
}
或
function Counter({A, B, C}) {
return (
<div> ...</div>
);
}
三、热加载的几种方法
1.使用webpack-dev-server
package.json中:


2.webpack-dev-server结合react-hot-loader,保存页面刷新前的状态:

3.使用webpack-hot-middleware


四、store
createStore(reducer, initialState, middleWare);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'))
Provider源码:
class Provider extends Component {
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}}
Provider.childContextTypes = {
store: React.PropTypes.object}
五、性能问题
1.virutal dom
熟悉React.js的都应该知道,React.js是一个UI = f(states)的框架,为了解决更新的问题,React.js使用了virtual dom,virtual dom通过diff修改dom,来实现高效的dom更新。
听起来很完美吧,但是有一个问题。当state更新时,如果数据没变,你也会去做virtual dom的diff,这就产生了浪费。
2.shouldComponentUpdate & PureRenderMixin
```javascript
this.state = {count: 0}
this.setState({count: 0});// 组件 state 并未被改变,但仍会触发 render 方法
```
为了避免这种性能上的浪费,React 提供了一个 shouldComponentUpdate 来控制触发 vdom re-render 逻辑的条件。于是 PureRenderMixin 作为一种优化技巧被使用。
PureRenderMixin的出现早于React.PureComponent,该插件属于历史保留,现在就使用React.PureComponent吧,这里也就提一下.
如果你的React组件的渲染函数是一个纯函数也就是说对于相同的值返回一样的结果同时不影响元素局,在某些场景下,你可以利用这个插件来极大地提升性能。
var PureRenderMixin = require('react-addons-pure-render-mixin');
React.createClass({
mixins: [PureRenderMixin],
render: function() {
return <div className={this.props.className}>foo</div>;
}
});
在底层,该插件实现了shouldComponentUpdate,在这里面,它比较当前的props、state和接下来的props、state,当两者相等的时候返回false,不进行更新。
ES6版本
import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
render() {
return <div className={this.props.className}>foo</div>;
}
}`
注意
仅仅是浅比较对象。如果对象包含了复杂的数据结构,深层次的差异可能会产生误判。仅用于拥有简单props和state的组件,或者当你知道很深的数据结构已经变化了的时候使用forceUpdate()。或者,考虑使用immutable objects(不变数据)来帮助嵌套数据快速比较。
[shouldComponentUpdate会跳过更新整个组件子树。确保所有的子组件也是“纯粹的”]这套路不多说。
用react-redux的connect方法包装后,会提供新的shouldComponentUpdate实现(默认是会返回true),用“浅层比较”(简单说就是js中的===操作符,使用浅层比较的原因是考虑到代码的复杂性和性能问题)的方式对比prop和上一次渲染所用的prop,如果是复杂对象,这种比较方式就只看prop是不是同一对象的引用,如果不是,哪怕这两个对象中的内容完全一样,也会被认为是两个不同的prop。例如:
i. <Foo style={{color: ‘red’}} />
利用react-redux的shouldComponentUpdate实现,每一次渲染都会认为style这个prop发生了变化,可以改进为下面的方式:
const fooStyle = {color: ‘red’} //确保只执行一次,不放在render中
<Foo style={fooStyle } />
同样的情况也存在与函数类型的prop,react-redux无从知道两个不同的函数是不是做着一样的事情,要想让它认为两个prop是相同的,就必须让这两个prop指向同样的一个函数。看ii。
ii. <Todos />中有多个<TodoItem onToggle = { () => onToggleTodo(item.id) } />
这种prop,每次都会产生一个新的函数,依然躲不过每次更新过程都要重新渲染的命运,即便每次这个函数都是做的同样的事情,可以改进为下面的方式:
<TodoItem id={item.id} onToggle = {onToggleTodo} />
const mapDispatchToProps = (dispatch, ownProps) => ({
onToggleItem: () => ownProps.onToggle(ownProps.id)
})
还有一种方式是,干脆不让Todos传递任何函数类型prop,点击事件完全由子组建TodoItem组件自己搞定,这种方式更符合内聚的要求,见iii。
iii. <TodoItem id={item.id} />
const mapDispatchToProps = (dispatch, ownProps) => ({
const {id} = ownProps;
onToggle: () => dispatch(toggleTodo(id))
})
export default connect(null, mapDispatchToProps)(TodoItem);
3.immutable.js解决了什么问题?
mutable data structure做diff需要遍历
immutable只要对比一下reference是不是一个就行了。
可维护性和性能上的提升:
- 在按引用来传递数据的场景中,存在因修改数据而带来影响范围不可控的副作用,因为你不知道谁还引用着这份数据,不知道你的修改会影响到谁,而Immutable.js能解决这个问题,能控制修改带来的影响范围,因为它每次修改都会创建一个新的对象,原对象不变。
比如,调用函数func(objData),如果objData是Immutable.js包装过的,func内部在修改objData时不必担心会影响func调用者所拥有的数据,同样,func调用者也可以在调用func之后随意修改objData,不必担心会影响func内部所拥有的数据。
举个反例,Backbone.js中的不少方法的最后一个参数都是options对象,用来支持对方法内部操作进行一些配置,但Backbone.js会在方法内部修改这个options对象,甚至作为事件回调函数的参数传递出去,一个对象在Backbone.js框架内部外部传进传出,修改这个对象时由于影响范围不可控,很可能会惊呼“我cao~谁改了我的数据?”。
类似地,Flux架构中数据也会经过多层的传递,component=>actions(其中可能有多层middleware)=>store,而多个component组成的树状结构也需要将数据一层层往子component传递。这种按引用传递数据,数据流经多层的场景,如果没有将引用类型的数据Immutable化,拿到一份引用类型的数据,修改起来心里总是不踏实。
这是Immutable.js在代码可维护性上带来的提升。
避免这个副作用的一种实现是按值传递,也就是拷贝一份再传递过去,有深层结构就深拷贝。深拷贝在只做局部修改的时候做了很多无用功,于是Immutable.js做了性能优化。网上找了个图,假如我们要修改左图中黄色节点的子节点4,那么Immutable.js只需要更新右图中的绿色节点,其余节点不需拷贝,继续复用。也就是说,Immutable.js会更新从根节点到所修改节点路径上的所有节点,由于修改了根节点,所以返回一个新对象,这也解释了为什么能控制副作用。

跟React.js的配合使用中提高性能:
1. 假如你在组件state中保存了一份有深层结构的引用类型的数据,如果没有Immutable.js,你需要深拷贝一份再做修改。而用Immutable.js将state中的数据包装一下,不需深拷贝就可以直接修改。
2. 由于修改后返回的是新对象,React.js只需要在oldState.obj === newState.obj这一层就能判断出obj产生了变化,不需要深入obj的深层结构。
------------------
P.S. 也不能光说好的,吐槽一下,Immutable.js设计的API很细很多,带来几种数据结构概念有差别但差别不是很大,库的体积很大,导致基本跟移动端无缘,要是能够将几种数据结构模块化一下,按需打包就好了,类似这样let ImmutableList from 'immutable/list'
使用 Immutable 的缺点
a. 需要学习新的 API
b. 增加了资源文件大小
c. 容易与原生对象混淆
这点是我们使用 Immutable.js 过程中遇到最大的问题。写代码要做思维上的转变。
虽然 Immutable.js 尽量尝试把 API 设计的原生对象类似,有的时候还是很难区别到底是 Immutable 对象还是原生对象,容易混淆操作。
Immutable 中的 Map 和 List 虽对应原生 Object 和 Array,但操作非常不同,比如你要用 `map.get('key')` 而不是 `map.key`,`array.get(0)` 而不是 `array[0]`。另外 Immutable 每次修改都会返回新对象,也很容易忘记赋值。
当使用外部库的时候,一般需要使用原生对象,也很容易忘记转换。
下面给出一些办法来避免类似问题发生:
a.使用 Flow 或 TypeScript 这类有静态类型检查的工具
b.约定变量命名规则:如所有 Immutable 类型对象以 `$$` 开头。
c.使用 `Immutable.fromJS` 而不是 `Immutable.Map` 或 `Immutable.List` 来创建对象,这样可以避免 Immutable 和原生对象间的混用。
4.reselector与范式
在上面的例子中,connect(mapStateToProps,mapDispatchToProps)(Counter) 中的 mapStateToProps 函数通过返回一个映射对象,指定了哪些 Store/State 属性被映射到 React Component 的 this.props,这个方法被称为 selector。selector 的作用就是为 React Components 构造适合自己需要的状态视图。selector 的引入,降低了 React Component 对 Store/State 数据结构的依赖,利于代码解耦;同时由于 selector 的实现完全是自定义函数,因此也有足够的灵活性(例如对原始状态数据进行过滤、汇总等)。
reselect 这个项目提供了带 cache 功能的 selector。如果Store/State和构造view的参数没有变化,那么每次Component获取的数据都将来自于上次调用/计算的结果。得益于 Store/State Immutable 的本质,状态变化的检测是非常高效的。
假如state中数据是如下形式:
{
id: 1,
completed: false,
}
现在要往其中添加新数据,反范式方法如下(容易读,不易改):
{
id: 1,
completed: false,
type: {
name: ‘紧急’,
color: ‘red’
}
}
范式方法如下:
{
id: 1,
completed: false,
typeId: 1
}
{
id: 1,
name: ‘紧急’,
color: ‘red’
}
要活的name和color,需要类似关系型数据库的join操作。
5.diff与key
React这个框架的核心思想是,将页面分割成一个个组件,一个组件还可能嵌套更小的组件,每个组件有自己的数据(属性/状态);当某个组件的数据发生变化时,更新该组件部分的视图。更新的过程是由数据驱动的,新的数据自该组件顶层向下流向子组件,每个组件调用自己的render方法得到新的视图,并与之前的视图作diff-比较差异,完成更新。这个过程就叫作reconciliation-调和。
React通过virtual dom来实现高效的视图更新。基本原理是用纯js对象模拟dom树,每当更新时,根据组件们的render方法计算出新的虚拟dom树,并与此前的虚拟dom树作diff,得到一个patch(差异补丁),最后映射到真实dom树上完成视图更新。而两棵树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题。但是在前端当中,很少出现跨越层级移动DOM元素的情况,所以React采用了简化的diff算法,只会对virtual dom中同一个层级的元素进行对比,这样算法复杂度就可以达到 O(n)。
由于React采用的diff算法是对新旧虚拟dom树同层级的元素挨个比较,碰到循环输出的元素时会有一些问题,比如列表。先来看一个例子:
// 旧v-dom
<ul>
<li>first</li>
<li>second</li>
</ul>
// 新v-dom
<ul>
<li>zero</li>
<li>first</li>
<li>second</li>
</ul>
React在diff两棵树时,发现原来的两个li元素都与新v-dom中对应位置上的两个li元素不同,就会对其修改,并向真实dom树中插入新的second节点。实际上,我们可能只是进行了在first之前插入新zero节点的操作,而现在进行了额外的修改操作。
React官方文档提示我们应该使用key属性来解决上述问题。key是一个字符串,用来唯一标识同父同层级的兄弟元素。当React作diff时,只要子元素有key属性,便会去原v-dom树中相应位置(当前横向比较的层级)寻找是否有同key元素,比较它们是否完全相同,若是则复用该元素,免去不必要的操作。
延续第一个例子,如果每个li元素都有key属性:
// 旧v-dom
<ul>
<li key="1">first</li>
<li key="2">second</li>
</ul>
// 新v-dom
<ul>
<li key="0">zero</li>
<li key="1">first</li>
<li key="2">second</li>
</ul>
现在React就知道了,新增了key为"0"的元素,而"1"与"2"仅仅移动了位置。
key必须是字符串类型,它的取值可以用数据对象的某个唯一属性,或是对数据进行hash来生成key。
<ul>
{list.map(v=> <li key={v.idProp}>{v.text}</li>)}
</ul>
但是强烈不推荐用数组index来作为key。如果数据更新仅仅是数组重新排序或在其中间位置插入新元素,那么视图元素都将重新渲染。来看下例子:
<ul>{list.map((v,idx)=><li key={idx}>{v}</li>)}</ul>
// ['a','b','c']=>
<ul>
<li key="0">a</li>
<li key="1">b</li>
<li key="2">c</li>
</ul>
// 数组重排 -> ['c','a','b'] =>
<ul>
<li key="0">c</li>
<li key="1">a</li>
<li key="2">b</li>
</ul>
React发现key为0,1,2的元素的text都变了,将会修改三者的html,而不是移动它们。
渲染同类型元素不带key只会产生性能问题,如果渲染的是不同类型的状态性组件,组件将会被替换,状态丢失。
class Box extends React.Component {
constructor(p) {
super(p)
this.state = {type: true}
this.handler = this.handler.bind(this)
}
handler() {
this.setState({
type: !this.state.type
})
}
render() {
return (<div>
<button onClick={this.handler}>haha</button>
{this.state.type ?
(<div><Son_1 /><Son_2 /></div>)
: (<div><Son_2 /><Son_1 /></div>)
}
</div>)
}
}
如上述代码,每次按下按钮,原Son_1与Son_2组件的实例都将被销毁,并创建新的Son_1与Son_2实例,不能继承原来的状态;而它们实际上只是调换了位置。给它们加上key可以避免问题:
{this.state.type ?
(<div><Son_1 key="1"/><Son_2 key="2"/></div>)
: (<div><Son_2 key="2"/><Son_1 key="1"/></div>)
}
所以,碰到数组->列表的映射,或是同级元素需要移位的情况,一定要给元素加上key属性!
另外,key和ref一样,是React保留的两个特殊prop,并没有预期让组件直接访问。
6.reconcoliation的缺点
其缺点就是无法发现某个子树移动位置的情况,如果某个子树移动了位置,那React就会重建这个子树,不过这种情况不常出现。
7.事件委托
在JSX中某组件使用onClick方法,但是并没有产生直接使用onclick的HTML,而是使用了事件委托的方式处理点击事件,五路有多少个onClick出现,其实最后都只在DOM树上添加了一个事件处理函数,挂在最顶层的DOM节点上。所有的点击事件都被这个事件处理函数捕获,然后根据具体组件分配给特定函数。
六、异步请求
1.async, await
2.sagas
import createSagaMiddleware from 'redux-saga';
createSagaMiddleware(); // 创建一个 Redux 中间件,将 Sagas 与 Redux Store 建立连接
take(pattern):
创建一条 Effect 描述信息,指示 middleware 等待 Store 上指定的 action。 Generator 会暂停,直到一个与 pattern 匹配的 action 被发起。
put(action):
创建一条 Effect 描述信息,指示 middleware 发起一个 action 到 Store。
call(fn, ...args):
创建一条 Effect 描述信息,指示 middleware 调用 fn 函数并以 args 为参数。
fork(fn, ...args):
创建一条 Effect 描述信息,指示 middleware 以 无阻塞调用 方式执行 fn。fork 类似于 call,可以用来调用普通函数和 Generator 函数。但 fork 的调用是无阻塞的,在等待 fn 返回结果时,middleware 不会暂停 Generator。
cancel(task):
创建一条 Effect 描述信息,指示 middleware 取消之前的 fork 任务。
七、fetch
export default function request(url: string, options?: RequestInit): Promise<{ data: any } | { err: ResponseError }> {
const f = window ? window.fetch : fetch;
let headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
}
Object.assign(headers, options.headers || {})
Object.assign(options, {
mode: 'cors',
headers: headers
});
return f(url, options)
.then(checkStatus)
.then(parseJSON)
.then((data) => ({data}))
.catch((err) => ({err}));
}
八、websocket
var io = require(‘socket.io-client’);
this.socket = io(config.apiServer, {
transports: ['websocket']
});
this.socket.on('connect', () => {
this.socket.emit('auth:begin', {token: token}) //send the jwt
.on('auth:ok', function () { // ...
})
.on('auth:fail', function (msg) {
this.props.logOut();
})
});
this.socket.on('update', (evt) => {
this.props.socketCallBack(evt);
})
九、生命周期图

1.componentWillMount
componentWillMount -> render -> componentDidMount
我们通常不用定义componentWillMount函数,这个时候没有任何渲染出来的结果,即使调用this.setState修改状态也不会引发重新绘制,一切都迟了,在这个函数中能做的,都可以提前到constructor中去做。
十、react服务端渲染
对于服务端,通过调用ReactDOMServer.renderToString方法把Virtual DOM转换成HTML字符串返回给客户端,从而达到服务端渲染的目的。
import { renderToString } from 'react-dom/server'import App from './App'
async function(ctx) {
await ctx.render('index', {
root: renderToString(<App />)
})
}
十一、特殊字符或自定义属性
JSX 与 HTML 非常相似,但是有些关键区别要注意。
注意:
关于 DOM 的区别,如行内样式属性 style,参考 DOM 区别
HTML 实体
HTML 实体可以插入到 JSX 的文本中。
<div>First · Second</div>
如果想在 JSX 表达式中显示 HTML 实体,可以会遇到二次转义的问题,因为 React 默认会转义所有字符串,为了防止各种 XSS 攻击。
// 错误: 会显示 “First · Second”
<div>{'First · Second'}</div>
有多种绕过的方法。最简单的是直接用 Unicode 字符。这时要确保文件是 UTF-8 编码且网页也指定为 UTF-8 编码。
<div>{'First · Second'}</div>
安全的做法是先找到 实体的 Unicode 编号 ,然后在 JavaScript 字符串里使用。
<div>{'First \u00b7 Second'}</div>
<div>{'First ' + String.fromCharCode(183) + ' Second'}</div>
可以在数组里混合使用字符串和 JSX 元素。
<div>{['First ', <span>·</span>, ' Second']}</div>
万不得已,可以直接插入原始HTML。
<div dangerouslySetInnerHTML={{'{{'}}__html: 'First · Second'}} />
自定义 HTML 属性
如果往原生 HTML 元素里传入 HTML 规范里不存在的属性,React 不会显示它们。如果需要使用自定义属性,要加 data- 前缀。
<div data-custom-attribute="foo" />
然而,在自定义元素中任意的属性都是被支持的 (那些在tag名里带有连接符或者 is="..." 属性的)
<x-my-component custom-attribute="foo" />
以 aria- 开头的 网络无障碍 属性可以正常使用。
<div aria-hidden={true} />
十二、React高级组件
1.代理方式的高阶组件
1.1操纵prop
const addNewProps = (WrappedComponent, newProps) => {
return class WrappingComponent extends React.component {
render() {
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
const FooComponent = addNewProps(DemoComponent, {foo: 'foo'})
const BarComponent = addNewProps(OtherComponent, {bar: 'bar'})
1.2访问ref
const refsHOC = (WrappedComponent) => {
return class HOCComponent extends React.Component {
constructor() {
super(...arguments)
this.linkRef = this.linkRef.bind(this)
}
linkRef(wrappedInstance) {
this._root = wrappedInstance
}
render() {
const props = {...this.props, ref: this.linkRef}
return <WrappedComponent {...props}/>
}
}
}
1.3抽取状态——connect
在傻瓜组件和容器组件的关系中,通常让傻瓜组件不要管理自己的状态,只要做一个无状态的组件就好,所有状态的管理都交给外面的容器组件,这个模式就是“抽取状态”,在这里尝试模拟一个简易版的connect高阶组件:
const doNothing = () => ({})
function connect(mapStateToProps=doNothing, mapDispatchToProps=doNothing) {
return function (WrappedCompnent) {
class HOCComponent extends React.Component {
//暂时没有实现shouldComponentUpdate函数
constructor() {
super(...arguments)
this.onChange = this.onChange.bind(this)
this.store = {}
}
componentDidMount() {
this.context.store.subscribe(this.onChange)
}
componentWillUnMount() {
this.context.store.unsubscribe(this.onChange)
}
onChange() {
this.setState({}) //只是为了驱动组件的更新过程
}
render() {
const store = this.context.store
const newProps = {
...this.props,
...mapStateToProps(store.getState()),
...mapDispatchToProps(store.dispatch)
}
return <WrappedCompnent {...newProps}/>
}
}
HOCComponent.contextTypes = {
store: React.PropTypes.object
}
return HOCComponent
}
}
1.4包装组件
const styleHOC = (WrappedComponent, style) => {
return class HOCComponent extends React.Component {
render() {
return (
<div style={style}>
<WrappedComponent {...this.props}/>
</div>
)
}
}
}
const style = {color: 'red'}
const NewComponent = styleHOC(DemoComponent, style)
2.继承方式的高阶组件
2.1.操纵props
const modifyPropsHOC = (WrappedComponent) => {
return class NewComponent extends WrappedComponent {
// 这种方式没有代理方式更清晰
render() {
const elements = super.render()
const newStyle = {
color: (elements && elements.type === 'div') ? 'red' : 'green'
}
const newProps = {...this.props, style: newStyle}
return React.cloneElement(elements, newProps, elements.props.children)
}
}
}
2.2操纵生命周期函数
//代理方式无法修改传入组件的生命周期函数
const onlyForLoggedinHOC = (WrappedComponent) => {
return class NewComponent extends WrappedComponent {
render() {
if(this.props.loggedIn) {
return super.render()
} else {
return null
}
}
}
}
//另一个例子
const cacheHOC = (WrappedComponent) => {
return class NewComponent extends WrappedComponent {
shouldComponentUpdate(nextProps, nextState) {
return !nextProps.useCache
}
}
}
从上面的例子可以看出,各方面来看代理方式要优于继承方式。
“优先考虑组合,然后才考虑继承”。
3.高阶组件显示名
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName ||
WrappedComponent.name ||
'Component'
}
HOCComponent.displayName = `Connect(${getDisplayName(WrappingComponent)}`
4.React Mixin
在React.createClass中,可以使用Mixin,作为一项设计原则,应该尽量把state从组件里抽取出来,尽量构建无状态组件,Mixin反其道行之,这是一个很大的缺陷。
const shouldUpdateMixin = {
shouldComponentUpdate: function() {
return !this.props.useCache
}
}
const SampleComponent = React.createClass({
mixins: [shouldUpdateMixin],
render: function() {
//...
}
})
4.以函数为子组件
高阶组件并不是唯一可用于提高React组件代码重用的方法。但是高阶组件也有一个缺点就是,对原组件的props有了固化的要求,也就是说,能不能把一个高阶组件作用于某个组件X,要先看一下X组件是不是能够接受高阶组件传来的props。
“以函数为子组件”的模式就是为了克服高阶组件的这种局限而生的。这种方式有个特点,要求必须有子组件的存在,而且这个子组件必须是一个函数。以倒计时功能为例:
class CountDown extends React.Component {
constructor() {
super(...arguments)
this.state = {count: this.props.startCount}
}
componentDidMount() {
this.intervalHandle = setInterval(() => {
const newCount = this.state.count - 1;
if(newCount >=0) {
this.setState({count: newCount})
} else {
window.clearInterval(this.intervalHandle)
}
}, 1000)
}
componentWillUnmount() {
if(this.intervalHandle) {
window.clearInterval(this.intervalHandle)
}
}
render() {
return this.props.children(this.state.count)
}
}
CountDown.propTypes = {
children: React.PropTypes.func.isRequired,
startCount: React.PropTypes.number.isRequired
}
// 显示倒计时从10到0
<CountDown startCount={10}>
{
(count) => <div>{count}</div>
}
</CountDown>
十三、辅助异步操作的库
redux-thunk
redux-saga
redux-effects
redux-side-effects
redux-loop
redux-observable
1.redux-thunk
thunk是一个计算机变成的属于,表示辅助调用另一个子程序的子程序,举例如下:
const f = (x) => {
return x() + 5
}
const g = () => {
return 3 + 4
}
f(g) // 结果是(3+4) + 5 = 12
上面代码中函数g就是一个thunk。下面是使用例子:
import thunkMiddleware from 'redux-thunk';
import {reducer as weatherReducer} from './weather/'; // reducer
import Perf from 'react-addons-perf; // 监测插件
const win = window;
win.Perf = Perf
const reducer = combineReducers({
weather: weatherReducer
});
const middlewares = [thunkMiddleware];
if (process.env.NODE_ENV !== 'production') {
middlewares.push(require('redux-immutable-state-invariant')());
}
const storeEnhancers = compose(
applyMiddleware(...middlewares),
(win && win.devToolsExtension) ? win.devToolsExtension() : (f) => f,
);
export default createStore(reducer, {}, storeEnhancers);
下面是redux-thunk实现代码:
function createThunkMiddleware(extraArgument) {
return ({dispatch, getState} => next => action => {
if(typeof action === 'function') {
return action(dispatch, getState, extraArgument)
}
return next(action)
})
}
2.redux-effects
作为纯函数的reducer可以帮助实现异步操作,其方法是让reducer函数的返回值包含包括异步操作的“指示”,然后由reducer的调用者去真正执行这个异步操作(如对服务器资源的访问)。
...待研究
3.redux-saga
3.1库比较大,有几十KB大小的体积,gzip压缩后也有7KB。
3.2学习曲线比较陡,如async与await,生成器函数(*function)
4.redux-observable
基于Rx.js库开发的,要求掌握响应式编程。
5.利用Promise实现异步操作
将Promise作为特殊处理的异步action对象,比redux-thunk更加易用,复杂度也不高。有下列一些库(不止):
redux-promise
redux-promises
redux-simple-promise
redux-promise-middleware
利用中间件,自己实现如下:
action:
export const fetchWeather = (cityCode) => {
const apiUrl = `/data/cityinfo/${cityCode}.html`;
return {
promise: fetch(apiUrl).then(response => {
if (response.status !== 200) {
throw new Error('Fail to get response with status ' + response.status);
}
return response.json().then(responseJson => responseJson.weatherinfo);
}),
types: [FETCH_STARTED, FETCH_SUCCESS, FETCH_FAILURE]
};
}
promise中间件:
function isPromise(obj) {
return obj && typeof obj.then === 'function';
}
export default function promiseMiddleware({dispatch}) {
return (next) => (action) => {
const {types, promise, ...rest} = action;
if (!isPromise(promise)||!(action.types && action.types.length === 3)) {
return next(action);
}
const [PENDING, DONE, FAIL] = types;
dispatch({...rest, type: PENDING});
return action.promise.then(
(result) => dispatch({...rest, result, type: DONE}),
(error) => dispatch({...rest, error, type: FAIL})
);
};
}
十四、redux中间件
1.使用中间方式一
用Redux提供的applyMiddleware来包装createStore产生一个新的创建Store的函数,以使用redux-thunk为例,其缺点是无法应用其他Store Enhancer,这种方式使用很少了:
import {createStore, applyMiddleware} from 'redux'
import thunkMiddleware from 'redux-thunk'
// 生成Store Enhancer
const configureStore = applyMiddleware(thunkMiddleware)(createStore)
const store = configureStore(reducer, initialState) //增强版store
2.使用中间方式二
把applyMiddleware的结果当作Store Enhancer,和其他Enhancer混合之后作为createStore参数传入,以同时使用redux-thunk和Redux Devtools增强器为例:
import {createStore, applyMiddleware, compose} from 'redux'
import thunkMiddleware from 'redux-thunk'
const win = window;//代码压缩时,用到win的地方都可以压缩,例如win压缩成w
const middlewares = [thunkMiddleware]
const storeEnhancers = compose(
applyMiddleware(...middlewares),
(win && win.devToolsExtension) ? win.devToolsExtension(): f => f
)
// 对于函数型,没有初始状态,直接作为initialState
const store = createStore(reducer, storeEnhancers)
3.Store Enhancer
从第一节也可以推测出,创建一个什么都没做的store enhancer代码如下:
const doNothingEnhancer = (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
return store
}

浙公网安备 33010602011771号