React-Router Documents

安装

npm install history react-router@latest
`</pre>

react-router 依赖 history 模块

<pre>`import {Router, Route, Link} from 'react-router'

可以从 lib 目录 require 需要的部分
`</pre>

import { Router } from 'react-router/lib/Router'

<pre>`import React from 'react'
import { Router, Route, Link } from 'react-router'

const App = React.createClass({/*..*/})
const About = React.createClass({/*...*/})

const Users = React.createClass({
  render(){
    return(
      &lt;div&gt;
        &lt;h1&gt;Users&lt;/h1&gt;
        &lt;div className = "master"&gt;
          &lt;ul&gt;
            {/* 在本应用中用 Link 去链接路由*/}
            {this.state.users.map(user =&gt; (
              &lt;li key={user.id}&gt;&lt;Link to={`/user/${user.id}`}&gt;{user.name}&lt;/Link&gt;&lt;/li&gt;
            ))}
          &lt;/ul&gt;
        &lt;/div&gt;
        &lt;div className ="detail"&gt;
          {this.props.children}
        &lt;/div&gt;
      &lt;/div&gt;
    )
  }
})

const User = React.createClass({
  componentDidMount(){
    this.setState({
      // 路由应该通过有用的信息来呈现, 比如 URL 的参数
      user: findUserById(this.props.params.userId)
    })
  },

  render(){
    return(
      &lt;div&gt;
        &lt;h2&gt;{this.state.user.name}&lt;/h2&gt;
        {/* 等等 */}
      &lt;/div&gt;
    )
  }
})

/**
 * 路由配置说明(不用加载整个配置, 只需要加载一个想要的跟路由, 也可以延迟加载这个配置)
 */

React.render(
  (&lt;Router&gt;
    &lt;Route path = "/" component = {App}&gt;
      &lt;Route path = "about" component = {About}/&gt;
        &lt;Route path = "users" component = {Users}&gt;
          &lt;Route path = "/user/:userId" component = {User}/&gt;
        &lt;/Route&gt;
        &lt;Route path = "*" component = {NoMatch}/&gt;
    &lt;/Route&gt;
  &lt;/Router&gt;),
  document.body
)
`</pre>

<pre>`import React from 'react'
import { render } from 'react-dom'

import { Router, Route, Link } from 'react-router'

const App = React.createClass({
  render(){
    return (
      &lt;div&gt;
        &lt;h1&gt;App&lt;/h1&gt;
        {/* 把 &lt;a&gt; 变成 &lt;Link&gt; */}
        &lt;ul&gt;
          &lt;li&gt;&lt;Link to="/about"&gt;&lt;/Link&gt;&lt;/li&gt;
          &lt;li&gt;&lt;Link to="/inbox"&gt;&lt;/Link&gt;&lt;/li&gt;
        &lt;/ul&gt;
        {/*
          接着用` this.props.children` 替换`&lt;Child&gt;`, this.props.children 就是 App 的子组件 Inbox 和 About
        */}

       {this.props.children}
      &lt;/div&gt;
    )
  }
})

React.render((
  &lt;Router&gt;
    &lt;Route path = "/" component = {App}&gt;
      &lt;Route path="about" component = {About}
      &lt;Route path="inbox" component = {Inbox}
    &lt;/Route&gt;
  &lt;/Router&gt;
))
`</pre>

<pre>`const Message = React.createClass({
  render(){
    return(
      &lt;h3&gt;Message&lt;/h3&gt;
    )
  }
})

const Inbox = React.createClass({
  render(){
    return(
      &lt;div&gt;
        &lt;h2&gt;Inbox&lt;/h2&gt;
        {this.props.children||'Welcome to your inbox'}
      &lt;/div&gt;
    )
  }
})

React.render((
  &lt;Router&gt;
    &lt;Route path="/" component = {App}&gt;
      &lt;Route path ="about" component = {About} /&gt;
      &lt;Route path ="inbox" component = {Inbox}&gt;
        &lt;Route path="message/:id" component={Message}/&gt;
      &lt;/Route&gt;    
    &lt;/Route&gt;
  &lt;/Router&gt;
),document.body)
`</pre>

访问 URL = inbox/message/Jkei 会匹配一个新路由, 其指向 App => Inbox => Message

<pre>`&lt;App&gt;
  &lt;Inbox&gt;
    &lt;Message params = {id: "Jkei"} /&gt;
  &lt;/Inbox&gt;
&lt;/App&gt;
`</pre>

### 获取 URL 参数

为了从服务器获取 message 数据, 我们首先需要知道他的信息, 当渲染组件的时候, React Router 自动向 Route 组件注入一些有用的信息, 尤其是路径中动态部分的参数, 比如上例中的`:id`

<pre>`const Message = React.createClass({
  componentDidMount(){
    const id = this.props.params.id

    fetchMessgae(id,function(err,message){
      this.setState({message:message})
    })
  }
})
`</pre>

### 路由配置

路由配置是一组指令, 用来告诉 router 如何匹配 URL, 以及匹配后的动作.

<pre>`import React from 'react'
import {Router, Route, Link} from 'react-router'

const App = React.createClass({
  render(){
    return(
      &lt;div&gt;
        &lt;h1&gt;App&lt;/h1&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;Link to="/about"&gt;About&lt;/Link&gt;&lt;/li&gt;
          &lt;li&gt;&lt;Link to="/inbox"&gt;Inbox&lt;/Link&gt;&lt;/li&gt;
        &lt;/ul&gt;
        {this.props.children}
      &lt;/div&gt;
    )
  }
})

const About = React.createClass({
  render(){
    return &lt;h3&gt;About&lt;/h3&gt;
  }
})

const Inbox = React.createClass({
  render(){
    return(
      &lt;div&gt;
        &lt;h2&gt;Inbox&lt;/h2&gt;
        {this.props.children || 'Welcome to your Inbox'}
      &lt;/div&gt;
    )
  }
})

const Message = React.createClass({
  render(){
    return &lt;h3&gt;Message {this.props.params.id}&lt;/h3&gt;
  }
})

React.render((
  &lt;Router&gt;
    &lt;Route path = "/" component = {App}&gt;
      &lt;Route path = "about" component = {About} /&gt;
      &lt;Route path = "inbox" component = {Inbox}&gt;
        &lt;Route path = "message/:id" component = {Message} /&gt;
      &lt;/Route&gt;
    &lt;/Route&gt;
  &lt;/Router&gt;
),document.body)
`</pre>

### 添加首页

当 URL 为 "/" 的时候, 渲染的 App 组件的 render 中的 this.props.children 还是 undefined, 这种情况我们可以使用`IndexRoute`来设置一个默认页

<pre>`import {IndexRoute} from 'react-router'
const Dashboard = React.createClass({
  render(){
    return &lt;div&gt;Welcome to the App!&lt;/div&gt;
  }
})

React.render((
  &lt;Router&gt;
    {/* 当 URL 为 / 的时候渲染 Dashboard*/}
    &lt;IndexRoute component = {Dashboard} /&gt;
    &lt;Route path="about" component = {About} /&gt;
  &lt;/Router&gt;
),document.body)
`</pre>

现在的 Sitemap 如下:

<table>
<thead>
<tr>
  <th>URL</th>
  <th>Component</th>
</tr>
</thead>
<tbody>
<tr>
  <td>/</td>
  <td>App => Dashboard</td>
</tr>
<tr>
  <td>/about</td>
  <td>App => About</td>
</tr>
</tbody>
</table>

### 将 UI 于 URL 解耦

如果我们可以将 `/inbox` 从 `/inbox/message:id` 中去除, 并且还能够让 Message 嵌套在 App->Inbox 中渲染, 那会非常赞, 绝对路径可以做到这一点

<pre>`React.render((
  &lt;Router&gt;
    &lt;Route path = "/" component = {App}&gt;
      &lt;IndexRoute component = {Dashboard} /&gt;
      &lt;Route path = "inbox" component = {Inbox} &gt;
        &lt;Route path = "/message:id" component = {Message} /&gt;
      &lt;/Route&gt;
    &lt;/Route&gt;
  &lt;/Router&gt;
),document.body)
`</pre>

在多层嵌套路由中使用感觉对路径可以提高逻辑性.

Simtemap 如下:

<table>
<thead>
<tr>
  <th>URL</th>
  <th>Component</th>
</tr>
</thead>
<tbody>
<tr>
  <td>/</td>
  <td>App => Dashboard</td>
</tr>
<tr>
  <td>/message/:id</td>
  <td>App => Inbox => Message</td>
</tr>
</tbody>
</table>

提醒, 绝对路径可能在动态路由中无法使用

同时, 上述配置存在一个问题, 当用户访问/inbox/message/6的时候回无法访问

使用&lt;Redirect>修正

<pre>`&lt;Route path = "indox" component = {Indox} &gt;
  &lt;Route path = "/message/:id" component = {Message} /&gt;
  &lt;Redirect from = "message/:id" to = "/message/:id" /&gt;
&lt;/Route&gt;
`</pre>

### 进入和离开的 Hook

Route 可以定义 `onEnter` 和 `onLeave` 两个 Hook, 这些 hook 会在页面跳转确认时触发一次,

在路由跳转过程中, `onLeave` hook 会在爱所有将离开的路由中触发, 从最下层的自路由开始直到最外层的父路由结束, 而`onEnter` hook 则从自外层的父路由开始直到最下层的子路由结束

比如从 /message/5 -> /about 依次触发
- /message/:id 的 onLeave
- /inbox 的 onLeave
- /about 的 onEnter

### 路由匹配原理

路由拥有三个属性来决定是否匹配一个 URL
- 嵌套关系
- 路径语法
- 优先级

#### 嵌套关系

当一个给定的 URL 被调用时, 整个集合(命中的部分)都会被渲染, 嵌套路由被描述成一种树形结构, React Router 会深度优化遍历整个路由配置来寻找一个与给定的 URL 匹配的路由

#### 路径语法

路由路径是匹配一个(或一部分) URL 的一个字符串模式, 大部分的路由路径都可以按照字面量理解, 除了一下几个特殊字符:
- `:paramName` - 匹配一段位于`/`,`?`, `#`之后的 URL, 命中的部分作为一个参数
- `()` - 内部的内容被认为是可选的
- `*` - 匹配任意字符(非贪婪)直到命中下一个字符或整个 URL 的末尾, 并创建一个 splat 参数

<pre>`&lt;Route path = '/hello/:name' /&gt; // 匹配 /hello/michael 和 /hello/john
&lt;Route path = '/hello/(:name)'/&gt; // 匹配 /hello, /hello/michael, /hello/john
&lt;Route path = '/files/*.*' /&gt; // 匹配 files/hello.js, files/hello.jpg
`</pre>

使用绝对路径可以使路由忽略嵌套关系

#### 优先级

兄弟路由的前一个优先级高

### Histories

React Router 是建立在 history 之上的, 一个 history 知道如何去监听浏览器地址变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由. 最后正确渲染组件

常用的 history 有三种形式
- createHashHistory
- createBrowserHistory
- createMemoryHistory

从 history 库中获取他们

<pre>`import createBrowserHistory from 'history/lib/createBrowserHistory'
`</pre>

#### createHashHistory

这是一个你会获得的默认 history, 如果不指定某个 history (即&lt;Router>{...}&lt;/Router>) 他用到的是 URL 中的 hash(#) 部分去创建形如 example.com/#/some/path 的路由

HashHistory 是默认的, 可以在服务器中不作任何配置就可以运行, 并且在全部浏览器中都可以使用, 但是不推荐实际生产中使用它, 因为每一个 web 应用都应该有目的地去使用 createBrowserHistory

#### 像`?_k=ckuvup` 没用的在 URL 中是什么?

当一个 history 通过应用程序的`pushState`或`replaceState`跳转时, 他可以在新的 location 中存储"lcoation state" 而不现实在 URL 中, 就像在一个 HTML 中 post 的表单数据

在 DOM API 中, 这些 hash history 通过 window.location.hash = newHash 很简单地被用于, 且不用存储他们的 location state, 但是我们希望全部的 history 都能够使用 location state, 因此要为每个 location 添加一个唯一的 key, 并把他们的状态存储在 session storage 中, 当访客点击前进或后退的时候, 可以恢复这些 location state.

#### createBrowserHistory

这是 react-router 创建浏览器应用推荐的 History, 使用 History API 在浏览器中被创建用于处理 URL, 新建一个像这样真实的 URL: example.com/some/path

##### 服务器配置

首先服务器应该能够处理 URL 请求, 处理应用启动最初的`/`这样的请求应该没问题,但是当用户来回跳转并在`/ accounts/23`刷新时, 服务器会受到来自`/account/23`的请求, 这时你就需要处理这个 URL 并在响应中包含 JS 程序代码

一个 express 的应用看起来可能是这样的:

<pre>`const express = require('express')
const path = require('path')
const port = process.env.PORT || 8080
const app = express()

// 通常用于加载静态资源
app.use(express.static(__dirname + '/public'))

// 在你应用 JS 文件中包含一个 script 标签的 index.html 中处理任何一个 route

app.get('*',function(request, response){
  response.sendFile(path.resolve(__dirname, 'public', 'index.html')
})

app.listen(port)
console.log('server started on port' + port)
`</pre>

当服务器找不到其他文件的时候, 服务器生成静态文件和操作 index.html 文件.

### 实例展示

<pre>`import React from 'react'
import createBrowserHistory from 'history/createBrowserHistory'
import { Router, Route, IndexRoute } from 'react-router'
import App from '../componnents/App'
import Home from '../componnents/Home'
import About from '../componnents/About'
import Features from '../componnents/Features'

React.render(
  &lt;Router history = {createBrowserHistory()} &gt;
    &lt;Route path = '/' component = {App} &gt;
      &lt;IndexRoute component = {Home} /&gt;
      &lt;Route path = 'about' component = {About} /&gt;
      &lt;Route path = 'features' component = {Features} /&gt;
    &lt;/Route&gt;
  &lt;/Router&gt;, document.body
)
`</pre>

### 默认路由(IndexRoute)与 IndexLink

如果设置了 IndexRoute => Home, 就要设置跳转到 Home 的路由'/', 即`&lt;Link to='/'&gt;Home&lt;/Link&gt;`, 他会一直处于激活状态(因为所有子路由都经过'/'), 我们仅希望在 Home 被渲染后激活并连接到他, 即需要在 Home 路由被渲染后才激活指向'/' 的链接, 请使用`&lt;IndexLink to='/'&gt;Home&lt;/IndexLink&gt;`

### 动态路由

React Router 适用于小型网站, 也可以支持大型网站

对于大型应用而言, 一个首当其冲的问题就是所需要加载的 JS 的大小, 程序应当只加载当前渲染页需要的的 JS, 有些开发者称之为"代码拆分", 在用户浏览过程中按需加载

对于底层细节的修改不应该需要他上面每一层级都进行修改, 举个例子, 为一个照片浏览页添加一个路径不应该影响到首页加载的 JS 大小, 也不能因为多个团队共用一个大型路由配置文件二造成合并时的冲突.

路由是个非常适合做代码拆分的地方: 他的责任就是配置好每个 view

React Routerzhogn 的路径匹配及组件加载都是异步完成的, 不仅允许你延迟加载组件, **并且可以延迟加载路由配置**. 在首次加载包中你只需要有一个路径定义, 路由会自动解析剩下的路径

Route 可以定义`getChildRoutes`, `getIndexRoute`, 和`getComponents` 这几个函数, 他们都是异步执行的, 并且只有需要时被调用, 这种方式成为"逐渐匹配"

React Router 会逐渐地匹配 URL 并且只加载该 URL 对应页面所需要路径配置和组件

如果配合 webpack 代码拆分工具使用的话, 一个原本繁琐的架构就会变得简明

<pre>`const CourseRoute = {
  path: 'course/:courseId',
  /* 当 get routes/indexRoute/Component 的时候 require 相应的资源
  getChildRoutes(location, callback){
    require.ensure([],function(require){
      callback(null, [
        require('./routes/Announcements'),
        require('./routes/Assignments'),
        require('./routes/Grades')
      ])
    })
  },

  getIndexRoute(location, callback){
    require.ensure([], function(require){
      callback(null, require('./components/Index'))
    })
  },

  getComponents(location, callback){
    require.ensure([],function(require){
      callback(null, requrie('./components/Course'))
    })
  }
}
`</pre>

### 跳转前确认

React Router 提供一个`routerWillLeave` 生命周期钩子, 这使得 React 可以拦截正在发生的跳转, 或者在离开 Route 前提示用户

`routerWillLeave` 返回值有一下两种:
1\. `return false` 取消此次跳转
2\. `return` 返回提示信息, 在离开 route 前提示用户进行确认

在 Route 组件中引入`Lifecycle` mixin 来安装这个钩子

<pre>`import { Lifecycle } from 'react-router'

const Home = React.createClass({
  mixins: [Lifecycle],
  routerWillLeave(nextLocation){
    if(!this.state.isSaved){
      return 'Your work is not saved!'
    }
  }
})
`</pre>

### 服务端渲染

服务端渲染与客户端渲染有些许不同, 需要
- 发生错误的时候发送一个`500`响应
- 需要重定向时发送一个`30x`响应
- 需要在渲染之前获得数据(用 router 完成)

### 组件生命周期

路由配置如下:

<pre>`&lt;Route path = '/' component = {App} &gt;
  &lt;IndexRoute component = {Home} /&gt;
  &lt;Route path = 'invoices/:invoiceId' component = {Invoice}/&gt;
  &lt;Route path = 'accounts/:accountId' component = {Account}/&gt;
&lt;/Route&gt;
`</pre>

#### 路由切换时, 组件生命周期的变化
  1. 当用户打开’/‘ 页面

























    Component LifeCycle
    App componentDidMount
    Home componentDidMount
    Invoice N/A
    Account N/A

  2. 当用户从’/‘ 跳转到 ‘/invoices/123’


























    Component LifeCycle
    App componentWillReceiveProps, componentDidUpdate
    Home componentWillUnmount
    Invoice componentDidMount
    Account N/A

    获取数据

    最简单的通过 router 获取数据的方法是通过组件生命周期 Hook 来实现.

    在 Invoice 中添加一个简单的数据获取功能

    `let Invoice = React.createClass({
    getInitialState(){

    return{
      invoice: null
    }
    

    },

    componentDidMount(){

    this.setState({
      this.fetchInvoice()
    })
    

    },

    componentDidUpdate(prevProps){

    let oldId = prevProps.params.invoiceId
    let newId= this.props.params.invoiceId
    if(newId !== oldId){
      this.fetchInvoice()
    }
    

    },

    componentWillUnmount(){

    this.ignorelastFetch = true
    

    },

    fetchInvoice(){

    let url = `/api/invoices/${this.props.params.invoiceId}`
    this.request = fetch(url, (err, data) =&gt; {
      if(!this.ingoreFetch){
        this.setState({invoice: data.invoice})
      }
    })
    

    },

    render(){

    return &lt;InvoiceView invoice = {this.state.invoice} /&gt;
    

    }
    })

React 性能优化 Tip

  1. 慎用 setState, 因为他容易导致重复渲染, 请将数据都交给 redux 管理, 再通过 props 传入. 记得使用 shouldComponentUpdate 比较以确定是否需要重新渲染

  2. 请将方法的 bind 一律置于 constructor 中, 可以避免重复绑定, 多个实例的构造函数是共享的.

  3. 只传递 component 需要的 props, 传递的太多, 或者传递的太深, 都会加重 shouldComponentUpdate 里面的数据复旦

  4. 路由控制与拆包, 当项目变得更大规模与复杂的时候, 我们需要设计成 SPA, 这时路由管理就变得非常重要, 这使得特定 url 参数可以对应特定页面

其他常见:
1. 使用 immutable 处理 props, state, store
2. 使用 pure-render-decorator 与 immutablejs 搭配使用
3. 慎用 setState
4. 仅传必要的 props
5. 将方法的 bind 置于 constructor

Middleware

You can include custom middleware functions to the dispatch method of your store.

The dispatch function is responsible for sending actions to one or more reducer functions for state changes. The composed specialized functions around the original dispatch method creates the new middleware capable dispatch method.

Source code for applyMiddleware(from Redux 1.0.1)

export default function applyMiddleware(...middlewares) {
  return (next) =&gt; 
    (reducer, initialState) =&gt; {
      var store = next(reducer, initialState);
      var dispatch = store.dispatch;
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: (action) =&gt; dispatch(action)
      };

      chain = middlewares.map(middleware =&gt; middleware(middlewareAPI));

      return {
        ...store,
        dispatch
      }
    }
}
`</pre>

Composing Functions

Functional programming is very literal and very mathematical. In the case of composing functions with math you can express two or more functions like this:

<pre>`given:

f(x) = x^2 + 3x + 1
g(x) = 2x

then:
(f · g)(x) = f(g(x)) = f(2x) = 4x^2 + 6x + 1
`</pre>

It is no coincidence that you can compose two or more functions in a similar fashion. Here is a very simple example of a function that composes two functions to return a new specialized function:

<pre>`var greet = function(x) { return `Hello, ${x}`}
var emote = function(x) { return `${x} :)`}

var compose = function(f,g){
  return function(x){
    return f(g(x))
  }
}
`</pre>

This was just to illustrate the basic concept. We will look at a more generic way to solve that issue by examining the Redux Code.

### Currying

Another powerful functional programming concepts is the idea of currying or partially applying argument values to a function. By currying we can create a new specialized function that has partial information supplied to it. Here is the canonical example of currying where we have an add function that curries the first operand parameter to create a specialized add function

<pre>`var curriedAdd = function(a){
  return function(b){
    return a + b;
  }
}

var addTen = curriedAdd(10);
addTen(10); // 20
`</pre>

By currying and composing your functions you can create powerful new functions that create a pipeline for data processing.

### Redux Dispatch Function

A Store in Redux have a dispatch method which is only concerned with the main execution task you are interested in. You dispatch actions to your reducer functions to update state of the application. Redux reducers function takes a state and action parameter and return a new resultant state.

<pre>`reducer:: state -&gt; action -&gt; state
`</pre>

You might dispatch an action that simply sends a message to remove an item in a list which could look like this:

<pre>`{type: types.DELETE_ITEM, id: 10}
`</pre>

The store will dispatch this action object to all of it's reducer functions which could affect state. However the reducer functions are only concerned with executing logic around this deletion. They typically don't care who did it, how long it took, or logging the before and after effects of the state changes. This is where middleware can help us to address non-core concerns.

### Redux middleware

Redux middleware is designed by creating functions that can be composed together before the main dispatch method is invoked.

Let's creating a very simple logger middleware function that can echo the state of your application before and after running your main dispatch function. Redux middleware functions have this signiture:

<pre>`middleware:: next -&gt; action -&gt; retVal
`</pre>

It might look something like this:

<pre>`export default function createLogger({getState}){
  return (next) =&gt; 
    (action) =&gt; {
      const console = window.console;
      const prevState = getState();
      const returnValue = next(action);
      const nextState = getState();
      const actionType = String(action.type);
      const message = `action ${actionType}`;

      console.log(`%c prev state`, `color: #9e9e9e`, prevState);
      console.log(`%c action`, `color: #03A9F4`, action);
      console.log(`%c next state`, `color:#4CAF50`, nextState)
    };
}
`</pre>

Notice that createLogger accepts the getState method which is injected by applyMiddleware.js and used inside closure to read the current state. This will return a new function with the `next` parameter which is used to compose the next chained middleware function or the main dispatch function.

This function returns a curried function that accepts the action object which can be read or modified before sending it to the next middleware function in the chain. Finally the main dispatch function is invoked with the action object.
  • First it captures the previous state
  • The action is dispatched to the next middleware function
  • All downstream middleware functions in the chain are invoked
  • The reducer functions in the store are called with the action payload
  • The logger middleware the gets the resulting next state

    Dissecting applyMiddleware.js

    `export default function applyMiddleware(...middlewares){
      return (next) => 
        (reducer, initialState) => {
          var store = next(reducer, initialState);
          var dispatch = store.dispatch;
          var chain = [];
    
          var middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
          };
    
          chain = middlewares.map(middleware => middleware(middlewareAPI));
    
          dispatch = compose(...chain, store.dispatch);
    
          return{
            ...store,
            dispatch
          };
        };
    }
    `

    The applyMiddleware function probably could have been named a little better, such as applyMiddlewareToStore.

    The applyMiddleware function returns a function that takes a mysterious next argument:

    `return (next) => (reducer, initialState) => {...}
    `

    The next argument will be a function that is used to create a store. By default you should look at the implementation for createStore. The final returned function will be like createStore and replace the dispatch function with it’s associated middlewares.

    Next we assign the store implementation to the function responsible for creating the new store. Then we create a variable to the store’s original dispatch function. Finally we setup an array to hold the middleware chain we will be creating.

    `var store = next(reducer, initialState)
    var dispatch = store.dispatch
    var chain = [];
    `

    This next bit of code injects the getState function and the original dispatch function from the store into each middleware function which you can optionally use in your middleware(the applyMiddleware function pass getState and dispatch to middlewares in it).

    The resultant middleware is stored in the chain array

    `var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    
    chain = middlewares.map(middleware => middleware({middlewareAPI}))
    `

    Now we create our replacement dispatch function with the information about hte middleware chain:

    `dispatch = compose(...chain, store.dispatch);
    `

    The magic to composing our middleware chain lies in this utility function supplied by Redux. Here is the implementation

    `export default function compose(...funcs){
      return funcs.reduceRight((compsed,f) => f(composed))
    }
    `

    The composing function will literally express your functions as a composition injecting each middleware as an argument to the next middleware in the chain. Order is important here when assembling your middleware function. Finally, the original store dispatch method is composed. This new looks like:

    `middlewareI(middlewareJ(middlewareK(store.dispatch)))(action)
    `

    The final thing to do is return the new store object with the overriden function:

    `return {
      ...store,
      dispatch
    }
    `

    Let’s add our logger middleware we started above into a custom store with the enhanced dispatch function.

    `import { createStore, applyMiddlware } from 'redux'
    import loggerMiddleware from 'logger'
    import rootReducer from './reducers'
    
    const createStoreWithMiddleware = applyMiddleware(loggerMiddleware)(createStore)
    
    export default function configureStore(initialState){
      return createStoreWithMiddleware(rootReducer, initialState);
    }
    
    const store = configureStore()
    `

    Asynchronous Middleware

    Once you get comfortable with the basics of Redux middleware you will likely want to work with asynchronous actions that involve some sort of asynchronous execution. In particular look at ‘redux-thunk’ for more details. Let’s say you have an action creator that has some async functionality to get stock quote information:

    `function fetchQuote(symbol){
      requestQuote(symbol);
      return fetch('......')
             .then(req => req.json())
             .then(json => showCurrentQuote(symbol, json));
    }
    `

    There is no obvious way here to dispatch an action that would be returned from the fetch which is Promise based. Plus we do not have a handle to the dispatch function. Therefore we can use the redux-thunk middleware to defer execution of these operations. By wrapping the execution in a function you can delay this execution:

    `function fetchQuote(symbol){
      return dispatch => {
        dispatch(requestQuote(symbol));
        return fetch('......').then(req=>req.json).then(json => dispatch(showCurrentQuote(symbol,json))
      }
    }
    `

    Remember that the applyMiddleware function will inject the dispatch and the getStat function as parameters int o the redux-thunk middleware. Now you can dispatch the resultant action objects to your store which conatains reducers. Here is the middleware function for redux-thunk that does this for you.

    `export default function thunkMiddleware({dispatch,getState}){
    return (next) =>

    action =&gt; {
      typeof action === 'function'? action(dispatch, getState):next(action)
    }
    

    }

If the action is a function it will be called with the dispatch and getState function..

Connect 相关

预备知识

回顾一下 Redux 基本用法

const reducer = (state = {count: 0}, action ) =&gt; {
  switch(action.type){
  case "INCRESE": return {count: state.count + 1}
  case "DECRESE": return {count: state.count - 1}
  default: return state;
  }
}

const actions = {
  increase: () =&gt; { type: 'INCREASE'},
  decrease: () =&gt; { type: 'DECREASE'}
}

const store = createStore(reducer);

store.subscribe(()=&gt;{
  console.log(store.getState())
});

store.dispatch(action.increase());
`</pre>

通过 reducer 创建一个 store, 每当我们在 store 上 dispatch 一个 action, store 内的数据就会通过 reducer 变化.

我们当然可以直接在 React 中使用 Redux, 在最外层容器中初始化 store, 然后将 state 上的属性作为 props 层层传下去

<pre>`class App extends Component{
  componentWillMount(){
    store.subscribe((state) =&gt; this.setState(state))
  }
  render(){
    return &lt;Comp state={this.state} onIncrease = {()=&gt;store.dispatch(actions.increase())} onDecrease = {()=&gt; store.dispatch(actions.decrease())}
  } /&gt;
}
`</pre>

但这不是最佳方式, 最佳方式是使用 react-redux 提供的 Provider 和 connect 方法

### 使用 React-redux

首先在最外层容器中, 把所有内容包裹在 Provider 组件中, 将之前创建的 store 作为 prop 传递给 Provider

<pre>`const App = () =&gt; {
  return(
    &lt;Provider store = {store}&gt;
      &lt;Comp /&gt;
    &lt;/Provider&gt;
  )
}
`</pre>

Provider 中的任何一个组件(比如这里的 Comp) 如果需要使用 state 中的数据, 就必须是 `被 connect 过的`组件, 使用 connect 方法对你编写的组件(MyComp)进行包装后的产物.

<pre>`class MyComp extends Component {
  // content...
}

const Comp = connect(...args)(MyComp);
`</pre>

可见 connect 是重中之重

### connect 详解

究竟 connect 做了什么

首先看一下函数的签名:

<pre>`connect([mapStatToProps], [mapDispatchToProps], [mergeProps], [options])
`</pre>

connect() 函数接受四个参数, 分别是

<pre>`mapStatToProps, mapDispatchToProps, mergeProps, options
`</pre>

<pre>`mapStatToProps(state, ownProps): stateProps
`</pre>

这个函数允许我们将 store 中的数据作为 props 绑定到组件上

<pre>`const mapStatToProps = (state) =&gt; {
  return {
    count: state.count
  }
}
`</pre>

这个函数的第一个参数就是 Redux 的 store, 我们从中摘取 count 属性, 因为返回了具有 count 属性的对象, 所以 MyComp 会有名为 count 的 props 字段

<pre>`class MyComp extends Component{
  render(){
    return &lt;div&gt;Count: {this.props.count}&lt;/div&gt;
  }
}

const Comp = connect(...args)(MyComp);
`</pre>

当然你不必将 state 原封不动传递给组件, 可以根据 state 中的数据, 动态的输出组件需要的(最小)属性

<pre>`const mapStateToProps = (state) =&gt; {
  return {
    greaterThanFive: state.count &gt; 5
  }
}
`</pre>

第二个参数 ownProps, 是 MyComp 自己的 props, 有时候 ownProps 也会对其产生影响, 比如当你在 store 中维护一个用户列表, 而你的组件 MyComp 只关心一个用户(通过 props 中的 userId体现)

<pre>`const mapStateToProps = (state, ownProps) =&gt; {
  // state 是{ userList:[{id:0,name;'王二'}]}
  return {
    user: _.find(state.userList, {id:ownProps.userId})
  }
}

class MyComp extends Component{
  static PropTypes = {
    userId: PropTypes.string.isRequired,
    user: PropTypes.object
  };
  render(){
    return &lt;div&gt;UserName: {this.props.user.name}&lt;/div&gt;
  }
}

const Comp = connect(mapStateToProps)(MyComp)
`</pre>

当 state 变化, 或者 ownProps 变化时, mapStateToProps 都会被调用, 计算出一个新的 stateProps , 在与 ownProps merge 后, 更新给 MyComp

这就是将 Redux store 中的数据连接到组件的基本方法

mapDispatchToProps(dispatch, ownProps): dispatchProps

connect 的第二个参数是 mapDispatchToProps, 功能是将 action 作为 props 绑定到 MyComp 上

<pre>`const mapDispatchToProps = (dispatch, ownProps) =&gt; {
  return {
    increase: (...args) =&gt; dispatch(actions.increase(...args)),
    decrease: (...args) =&gt; dispatch(actions.decrease(...args))
  }
}

class MyComp extends Component{
  render(){
    const {count, increase, decrease} = this.props
    return (
      &lt;div&gt;
        &lt;div&gt;Count:{count}&lt;/div&gt;
        &lt;button onClick={increase}&gt;+&lt;/button&gt;
        &lt;button onClick={decrease}&gt;-&lt;/button&gt;
      &lt;/div&gt;
    )
  }
}

const Comp = connect(mapStateToProps, mapDispatchToProps)(MyComp)
`</pre>

由于 mapDispatchToProps 方法返回了具有 increase 属性和 decrease 属性的对象, 这两个属性也会成为 MyComp 的 props

如上所示, 调用 actions.increase()只能得到一个 action 对象{type:'INCREASE'}, 要触发这个 action 必须要通过 dispatch 调用, dispatch 正式 mapDispatchToProps 的第一个参数, 但是为了不让 MyComp 组件感知到 dispatch 的存在, 我们需要将 increase 和 decrease 方法包装一下, 使之成为直接可以被调用的函数(即触发该方法就会触发 dispatch)

Redux 本身提供了 bindActionCreator 函数, 来将 action 包装成可以直接被调用的函数

<pre>`import {bindActionCreator} from 'redux'
const mapDispatchToProps = (dispatch,ownProps) =&gt; {
  return bindActionCreator({
    increase: actions.increase,
    decrease: actions.decrease
  })
}

同样当 ownProps 变化时, 该函数也会被调用, 生成一个新的 dispatchProps( 在与 stateProps 和 ownPorps merge 后), 更新给 MyComp. 注意 action 的变化不会引起上述过程, 默认 action 在组件的生命周期中是固定的

[mergeProps(stateProps, dispatchProps, ownProps): props]

之前说过不管是 stateProps 还是 dispatchProps, 都需要和 ownProps merge 之后才会被赋给 MyComp, connect 的第三个参数就是来做这个事情的, 通常情况下不需要传这个参数, connect 会使用 Object.assign 方法代替该方法.

其他

最后还有一个 options 参数, 比较简单, 一般也不会用到.

参考

部署React + Redux 的 Web 开发环境

好的部署需要做到两点:
1. 性能优化: 包括代码执行速度, 页面载入速度
2. 自动化: 减少重复工作

使用 React+Redux 的时候往往会用到其调试工具 Redux DevTools, 在手动配置 DevTools 时需要针对 Store 和 Component 进行一些配置, 然而这些都是为了方便调试的, 生产环境下我们不希望加入这些东西, 所以建议从代码上隔离 development 和 production 环境:

containers/
  Root.js
  Root.dev.js
  Root.prod.js
  ...

store/
  index.js
  store.dev.js
  store.prod.js
`</pre>

同时采用单独的入口文件, 比如上面的 containers/Root.js, 按需要加载不同环境的代码

<pre>`if (process.env.NODE_ENV === 'production'){
  module.exports = require('./Root.prod');
} else {
  module.exports = require('./Root.dev');
}
`</pre>

有一个细节需要注意: ES6 语法不支持在 if 中书写 import 语句, 所以这里采用了 CommonJS 的模块引入方法 require.

另一个需要注意的地方是按需要 import, 否则可能在打包的时候引入不必要的代码

使用 webpack 进行打包工作

<pre>`webpack --config webpack.config.prod.js --progress
`</pre>

注意要为不同的环境准备不同的 webpack 配置文件. 比如 `webpack.config.dev.js` 和 `webpack.config.prod.js` . 看一下比较关键的配置选项

### devtools

启用 source-map, 这样 webpack 会生成两个包
- bundle.js
- bundle.js.map
把用于定位源码的 source map 分离出去, 减少 bundle.js 的体积

source-map 只会在浏览器 devtools 激活时加载, 并不会影响正常页面的饿加载速度.

### plugins

<pre>`plugins:[
  new webpack.optimize.UglifyJsPlugin({
    compress:{
      warning: false
    }
  }),
  new webpack.optimize.DedupePlugin(),
  new webpack.optimize.OccurenceOrderPlugin()
]
`</pre>

### 不要忽视 NODE_ENV

NODE_ENV 其实就是一个环境变量, 在 Node 中 可以通过`process.env.NODE_ENV`获取, 目前大家汪汪用这个变量区分当前是 development 还是 production.

通过 webpack 的 DefinePlugin 设置环境变量:

<pre>`plugins:[
  ...
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stingify('production')
  })
]

### 添加 hash
前端公认的 Best Practice 就是给资源打上 hash 标签, 这对缓存静态资源很有用

1\. 给 bundle.js 添加 hash 标签
`</pre>

output{
  path: ...
  filename: 'bundle.[hash].js'
}

<pre>`使用 html-webpack-plugin 这个插件自动生成带有
`</pre>

&lt;script src='bundle.[hash].js>&lt;/script>'的 html 文件

<pre>`配置如下:
`</pre>

plugins:[
  ...
  new HtmlWebpackPlugin({
    title:'html title',
    template:'./template.html',
    favicon: './static/images/logo.ico'
  })
]

<pre>`配置 template.html
`</pre>

<!DOCTYPE html>
&lt;html>
&lt;head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
&lt;/head>
&lt;body>

<div id="react-container"></div>

&lt;/body>
&lt;/html>

<pre>`
如果需要在 React Component 中添加图片, 建议采用
`</pre>

import LOGO.png from './static/images/logo.png'
&lt;img src={LOGO.png} alt="" />

<pre>`使用 import 或 require 可以让 webpack 对图片进行打包

使用 file-loader 可以对图片添加 hash 标签然后返回相应的 URL

module:{
loaders:[
{
test: /.(png|jpg),
loader:”file?name=[hash].[ext]”
}
]
}
`

引入 CSS

可以直接在 js 文件中 require('./foo.css'), 然后使用 style-loader 和 css-loader 进行处理, 最终效果是将 css 嵌入到 HTML 的 style 标签中, 这样就不需要使用 hash

CSS 较大的话, 可以考虑使用 extract-text-webpack-plugin 插件将 CSS 合并后独立为一个文件, 并自定在页面加载的时候添加 link 标签

Redux 搭配 React

第一步是初始化 react 组件

getInitialState(){
  return{
    item: store.getState()
  }
}
`</pre>

第二步是在组件 render 后调用 subscribe 函数, 每次 redux 发生 dispatch 的时候都要调用 react 的 setState

<pre>`componentDidMount(){
  var unsubscribe = store.subscribe(this.onChange)
}
onChange(){
  this.setState({
    item: store.getState()
  })
}

React 会根据 State 的变化重新 render 组件
this.state.item.todos.map()

React Ajax

组件的数据来源, 通常是通过 AJAX 请求从服务器获取, 可以使用 componentDidMount 方法设置 AJAX 请求, 等到请求成功, 再用this.setState()方法重新渲染 UI.

var UserGist = React.createClass({
  getInitialState(){
    return {
      userName: '',
      lastGistUrl: ''
    }
  },

  componentDidMounted(){
    $.get(this.props.source, function(result){
      var lastGist = result[0];
      if(this.isMouted()){
        this.setState({
          userName: lastGist.owner.login,
          lastGistUrl: lastGist.html_url
        });
      }
    }.bind(this));
  },

  render(){
    return (
      &lt;div&gt;
        {this.state.userName}'s last Gist is 
        &lt;a href={this.state.lastGistUrl}&gt;here&lt;/a&gt;
      &lt;/div&gt;
    );
  }
});

React.render(
  &lt;UserGist source= '....' /&gt;, document.body
)

React 表单

用户在表单输入的内容, 属于用户与组件的互动, 要用this.setState()修改

var Input = React.cr而奥特Class({
  getInitialState(){
    return {
      value: 'Hello'
    };
  },

  handleChange(event){
    this.setState({
      value: event.target.value;
    })
  },

  render(){
    return(
      &lt;div&gt;&lt;input type='text' onChange={this.handleChange} value = {this.state.value} /&gt;&lt;p&gt;{this.state.value}&lt;/p&gt;&lt;/div&gt;
    );
  }
});

ReactDOM.render(
  &lt;Input /&gt;, document.body
);

PropTypes 与 getDefaultProps

组件的属性可以接受任意值. 有时候我们需要一种机制, 验证别人使用组件时, 提供的参数是否符合要求.

组件类的 PropTypes 属性, 就是用来验证组件实例的属性是否符合要求的.

var MyTitle = React.createClass({
  propTypes: {
    title: React.PropTypes.string.isRequried,
  },

  render() {
    return &lt;h1&gt; {this.props.title} &lt;/h1&gt;;
  }
});
`</pre>

上面的 `MyTitle` 组件有一个 `title` 属性, `PropTypes` 告诉 React, 这个 `title` 属性是必须的, 而且数据类型是字符串.

如果在实例化的时候设置 `title=123`, 控制台会显示一行错误信息

<pre>`Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.
`</pre>

此外, `getDefaultProps` 可以用来设置组件属性的默认值

<pre>`var MyTitle = React.createClass({
  getDefaultProps() {
    return {
      title: 'Hello World'
    };
  },

  render(){
    return &lt;h1&gt;{this.props.title}&lt;/h1&gt;;
  }
})

ReactDOM.render(&lt;MyTitle /&gt;, document.body); // 'Hello World'

Refs to Components

The DOM returned from render() is virtual, so called Virtual DOM, and we can get any factual input(value) from a virtual object, such as access to value from a component.

The ref returned from ReactDOM.render

ReactDOM.render() will return a reference to your component’s backing instance(or null for stateless components)

var myComponent = ReactDOM.render(&lt;MyComponent /&gt;, myContainer)

However, that the JSX doesn’t return a component. It’s just a ReactElement: a lightweight representation that tells React what the mounted component should look like.

var myComponentElement = &lt;MyComponent title = 'title'/&gt;; //This is just a ReactElement
`</pre>

<pre>`var myComponentInstance = ReactDOM.render(myComponent, myContainer); //a Virtual DOM
`</pre>

### The ref Callback Attribute

React supports a special attribute that you can attach to any component. The 'ref' attribute ca be a callback function, and this callback will be executed **immediately after the component is mounted**. The referenced component will be passed in as a parameter, and the callback function may use the component immediately, or save the reference for future use.

It's as simple as adding a `ref` attribute to anything returned from render:

<pre>`render: function(){
  return (
    &lt;TextInput ref={function(input){
      if(input != nill){
        input.focus();
      }
    }} /&gt;
  );
},
`</pre>

or use ES6 arrow funciton:

<pre>`render: function(){
  return &lt;TextInput ref={(c) =&gt; this._input = c} /&gt;;
},
componentDidMount: function{
  this._input.focus();
}
`</pre>

### The ref String Attribute

React also supports using a string as a ref prop on any component.
Add `ref` attribute to component so you can access to them via `this.refs`
  1. Assign a ref attribute to anything returned from render such as:

    `<input ref='myInput'>
    `
  2. In some other code, access the backing instance via this.refs as in

    `var input = this.refs.myInput;
    var inputValue = input.value;
    `

    A Complete Example

    `var MyComponent = React.createClass({
    handleClick: function(){

    if(this.myTextInput != null){
      this.myTextInput.focus()
    }
    

    },
    render: function(){

    return(
      &lt;div&gt;
        &lt;input type='text' ref={(ref) =&gt; this.myTextInput = ref} /&gt;
        &lt;input type='button' value='Focus the text input' onClick = {this.handleClick} /&gt;
      &lt;/div&gt;
    );
    

    }
    });

    ReactDOM.render(<MyComponent />, document.getElementById(‘example’));

Note: ref will be destroyed as component unmounted.

Component API

setState(nextState(obj)[, callback(function)])

设置 nextState 的某个键值, 然后下一次 EventLoop 的时候 this.state 会被更新
注意, setState 方法中的回调函数在组件重新渲染后才被调用

replaceState(nextState(obj)[, callback(function)])

类似于 setState(), 但是会删除已存在的 state 键, 相当于重新初始化State

forceUpdate([callback(function)])

强制渲染组件
注意该方法的回调函数也是在组件重新渲染完成后调用

getDOMNode()

返回组件所处的 DOM 元素

isMounted()

返回一个 Boolean 值, 如果组件加载到了 DOM, isMount() 返回 true.

React 顶层 API

React.createClass(obj)

创建一个 ReactClass(组件类), 参数是一个对象且必须有 render 属性. 该方法必须返回一个封闭的容器或 null/false(表示不渲染)

在该方法中, 所有的 this 都会在最终调用的时候绑定到创建的组件的构造器上.

React.createElement(TYPE(string|ReactClass)[, PROPS[, CHILDREN(ReactElement)]])

创建一个指定类型的 React 元素(要区分 ReactClass(通性) 和 ReactElement( 个性) 和 Virtual DOM(实例), 通过 ReactClass 创建 ReactElement, 然后实例化成 Virtual DOM: 比如 ButtonComponent 是 Class, 而<ButtonCompoennt />则是 Element, 最后通过 ReactDOM.render() 生成Virtual DOM)
第三个参数 Children 可以是人一个书的 React 元素

React.createElement('div', null, 
  React.createElement('p', null, 
    React.createElement(Component, {a:1})
  )
)
`</pre>

### React.cloneElement(TYPE(ReactElement)[, PROPS(object)[, CHILDREN(ReactElement)]])

克隆并返回一个新的 ReactElement(内部子元素也会随之克隆), 新返回的元素会保留就元素的 props, ref, key, 也会集成新的 props( 如果第二个参数不为 null)

<pre>`var Hello = React.createClass({
  render: function(){
    var span = &lt;span a = '1'&gt;Span&lt;/span&gt;;
    var newSpan = React.cloneElement(span,{b:'2'}, &lt;em&gt;EM&lt;/em&gt;);
    console.log(newSpan.props);
    return &lt;div&gt;Hello {span},{newSpan}&lt;/div&gt;;
  }
});
`</pre>

注意 createElement的第一个参数必须是字符串或者 ReactClass, 而 cloneElement 的第一个参数只能是 ReactClass

### React.createFactory(TYPE(string|ReactElement))

返回一个某种类型的 ReactElement 工厂函数, 可以利用返回的函数来创建一个 ReactElement( 配置 props 和 children)

<pre>`var Component = React.createClass({
  render:function(){
    return this.props.a == 1? &lt;p&gt;123&lt;/p&gt; : null;
  }
});

var p = React.createFactory(Component),
    ReactElementP = p{{a:1}},
    div = React.createFactory('div'),
    ReactElementDiv = div(null, ReactElementP);

React.render(
  ReactElementDiv, document.body
);
`</pre>

### React.render(REACTELEMENT(ReactElement), CONTAINER(DOMElement)[, CALLBACK(function)])

渲染一个 ReactElement 到 container 指定的 DOM 中, 并返回一个到该组件的引用. 如果提供了可选的回调函数, 则该函数会在组件渲染或更新之后调用

如果我们希望在组件外部可以获得组件内部(能通过 this 访问)的东西, 可以将 React.render 的返回值赋予一个变量, 在后续的调用中访问变量即可.

### React.unmountComponentAtNode(CONTAINER(DOMElement)

从 container 指定的 DOM 中移除已经加载的 ReactElement, 清楚相应的事件处理器和 state, 如果在 container 中没有存在的组件, 则不作处理
如果组件清除成功, 则返回 true

### React.renderToString(REACTELEMENT(ReactElement))

React 为服务端提供的一个方法, 可以直接输出 ReactElement 为 HTML 字符串, 将这些标记发送(比如 res.write(HTMLString))给客户端, 可以获得更快的页面加载速度, 并有利于搜索引擎的抓取

<pre>`var com = &lt;Component /&gt;
comHTML = React.renderToString(com) //HTMLString
`</pre>

### React.renderToStaticMarkup

类似于 React.renderToString, 但只生成纯粹的 HTML 标记字符串, 不会包含类似 dta-reactid 之类的 React 属性, 从而节约字节数

### React.initializeTouchs(ShouldUserTouch(boolean))

开启或关闭 React 的触摸事件机制, 传入参数 true 使 React 响应 Touch 事件

### React.Chilren

#### React.Children.map(obj children, function[, object context])

遍历子元素, 映射为一个新的子元素集合

<pre>`var Component = React.createClass({
  render: function(){
    return (
      &lt;ul&gt;
        {React.Children.map{
          this.props.children, function(){
            return &lt;li&gt;{child}&lt;/li&gt;
            }
          }
        }
      &lt;/ul&gt;
    )
  }
})

React.Children.forEach(obj children, function[, obj context])

遍历子元素, 对每个子元素执行回调, 但不会返回一个新的集合

React.Children.count(obj children)

返回子元素总个数

Updating With React.render

What does React.render do

  1. First, validate the inputs
  2. If there was no previous component, render as a new component
  3. Otherwise, compare the next component to the previous component using “shouldCUpdateComponent”
  4. If “shouldUpdateComponent” is true, update the component using ReactComponent.updateComponent. Otherwise, unmount and continue to render as a new component

React.render gives you a declarative way to use React’s update flow at the top level, similar to how re-rendering works inside a component. Many times, however, you can create a wrapper component so that you only have a single React.render call. This allows you to keep the logic inside components, which may be cleaner.

React 组件生命周期

Mounting/组件初始化相关

componentWillMount
componentDidMount

Updating/组件更新相关

componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate

Unmounting/组件移除相关

componentWillUnmount

Initial 相关

getDefaultProps
getInitialState

componentWillMount

在组件初始化前执行, 但仅执行一次, 即使多次重复渲染该组件,或者改变组件的 state
如果希望该回调能执行多次, 可以使用 React.unmountComponentAtNode 移除已有的组件, 然后重新 render

componentDidMount

在组件初始化完成时触发, 同样只能触发一次

componentWillReceiveProps

在组件接收到新的 props 的时间点之前调用, 注意初始化的时候不会触发(此时初始化 props, 而不是获得新的 props), 但是如果组件重复渲染(没有移除), 则会触发此事件

shouldComponentUpdate

组件接收到新的 props 或 state 的时候(此时还没进行下一次 render) 会立即调用, 然后通过返回布尔值决定是否要重新渲染当前组件
该接口接收两个参数, 第一个参数表示新的props, 第二个表示新的 state

shouldComponentUpdate:function(){return true} //重新渲染组件

componentWillUpdate

shouldComponentUpdate 返回 true 的时候调用, 此时 props 和 state 都是更新后的值, 而组件尚未重新渲染

componentDidUpdate

重新渲染后才会触发

componentWillUnmount

组件被移除之前触发, 用于做一些必要的清理, 比如无效的定时器等

getDefaultProps

该方法是最先触发的, 可以在该方法中 return 一个对象作为组件的默认 Props(当然如果有从父组件传来的 props, 则以传进来的为主)
只在组件初始化的时候执行一次

getInitialState

Component Children

this.props.children

Children allow you to pass components as data to other components, just like any other prop you use. The special thing about children is that React provides support through its ReactElement API and JSX. XML children translate perfectly to React children.

var MyDiv = React.createClass({
  render: function(){
      return &lt;div&gt;{this.props.children}&lt;/div&gt;
}
});

ReactDOM.render(
  &lt;MyDiv&gt;
      &lt;span&gt;Hello&lt;/span&gt;
      &lt;span&gt;World&lt;/span&gt;
  &lt;/MyDiv&gt;,
  node  
);
`</pre>

### this.props.children

If you look at the **JSX** transform, you'll find that XML children are appended as arguments to the _React.createElement_ call.
 Most often, your component will render content and include the children in the _render_ output. This is a great way to create UI components, like cards, headers, and buttons.

Occasionally, you may want to interact with the children, maybe mutating or separating them. Be careful here and remember to treat **this.props.children** as an **opaque data structure**. Instead of working with **this.props.children** directly, use the **React.Children** utilities to work with children.

<pre>`var ListComponent = React.createClasss({
  render: function(){
      var children = React.Children.map(
        this.props.children,
        function(child){
            return &lt;li&gt;{child}&lt;li&gt;
        }
    );
    return &lt;ul&gt;{children}&lt;/ul&gt;;
  }
  });

  var middleChildren = [
  &lt;strong key='2'&gt;Child 2&lt;/strong&gt;,
  &lt;a href='#' key='3'&gt;Child 3&lt;/a&gt;
  ];

  ReactDOM.render(
  &lt;ListComponent&gt;
    &lt;span&gt;Child 1&lt;/span&gt;
    {middleChildren}
    &lt;em&gt;Child 4&lt;/em&gt;
  &lt;/ListComponent&gt;,
  document.body
);
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×