来源于互联网

一、npm的配置

切换淘宝镜像源

1
2
3
4
5
npm config set registry https://registry.npm.taobao.org

npm config get registry

npm install -g cnpm --registry=https://registry.npm.taobao.org

使用npm安装react

1
cnpm install react react-dom --save

二、开发环境配置

这里使用create-react-app初始化项目

1
npm install create-react-app -g

安装完成之后就可以在命令行使用 create-react-app 了,首先选择一个合适的目录,然后只需要简单地输入

1
create-react-app yourfilename

三、认识JSX

3.1 JSX 简介

JSX 其是一个语法扩展,它既不是单纯的字符串,也不是HTML,虽然长得和 HTML 很像甚至基本上看起来一样。但事实上它是 React 内部实现的一种,允许我们直接在 JS 里书写 UI 的方式

3.2 JSX 属性

JSX 的标签同样可以拥有自己的属性

1
const title = <h1 id="main">React Learningh1>
1
2
// 注意是 className 而不是 class
const title = <h1 className="main">React Learningh1>

3.3 JSX 嵌套

JSX 的标签也可以像 HTML 一样相互嵌套,一般有嵌套解构的 JSX 元素外面,我们习惯于为它加上一个小括号

1
2
3
4
5
6
const title = (

"main"

>React Learning</h1>

Let's learn JSXp>


</div>
)

需要注意的是,JSX 在嵌套时,最外层有且只能有一个标签,否则就会出错

1
2
3
4
5
// 这是一个错误示例
const title = (

"main"

>React Learning</h1>

Let's learn JSXp>


)

3.4 JSX表达式

JSX 元素中,我们同样可以使用 javascript 表达式,在 JSX 当中的表达式需要用一个大括号括起来

1
2
3
4
5
6
7
8
9
10
function sayhi(name) {
return 'Hi,' + name
}

const title = (

"main"

>React Learning</h1>

Let's learn JSX. {sayhi('you')}span>p>


</div>
)

四、组件类型

4.1 函数定义与类定义组件

第一种函数定义组件,非常简单啦,我们只需要定义一个接收props传值,返回React元素的方法即可

1
2
3
function Title(props) {
return <h1>Hello, {props.name}h1>
}
1
2
// 甚至使用ES6的箭头函数简写之后可以变成这样
const Title = props =>

Hello, {props.name}</h1>


第二种是类定义组件,也就是使用ES6中新引入的类的概念来定义React组件

  • 组件在定义好之后,可以通过JSX描述的方式被引用,组件之间也可以相互嵌套和组合
1
2
3
4
5
class Title extends React.Component {
render() {
return <h1>Hello, {this.props.name}h1>
}
}

4.2 展示与容器组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 展示组件

class CommentList extends React.Component {
constructor(props) {
super(props)
}

renderComment({body, author}) {
return <li>{body}—{author}li>
}

render() {
return <ul> {this.props.comments.map(this.renderComment)} ul>
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 容器组件
class CommentListContainer extends React.Component {
constructor() {
super()
this.state = { comments: [] }
}

componentDidMount() {
$.ajax({
url: "/my-comments.json",
dataType: 'json',
success: function(comments) {
this.setState({comments: comments})
}.bind(this)
})
}

render() {
return <CommentList comments={this.state.comments} />
}
}

展示组件

  • 主要负责组件内容如何展示
  • props接收父组件传递来的数据
  • 大多数情况可以通过函数定义组件声明

容器组件

  • 主要关注组件数据如何交互
  • 拥有自身的state,从服务器获取数据,或与redux等其他数据处理模块协作
  • 需要通过类定义组件声明,并包含生命周期函数和其他附加方法

那么这样写具体有什么好处呢?

  • 解耦了界面和数据的逻辑
  • 更好的可复用性,比如同一个回复列表展示组件可以套用不同数据源的容器组件
  • 利于团队协作,一个人负责界面结构,一个人负责数据交互

4.3 有状态与无状态组件

有状态组件

这个组件能够获取储存改变应用或组件本身的状态数据,在React当中也就是state,一些比较明显的特征是我们可以在这样的组件当中看到对this.state的初始化,或this.setState方法的调用

无状态组件

这样的组件一般只接收来自其他组件的数据。一般这样的组件中只能看到对this.props的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 有状态组件
class StatefulLink extends React.Component {
constructor(props) {
super(props)
this.state = {
active: false
}
}
handleClick() {
this.setState({
active: !this.state.active
})
}
render() {
return <a
style={{ color: this.state.active ? 'red' : 'black' }}
onClick={this.handleClick.bind(this)}
>
Stateful Link
a>
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 无状态组件
class StatelessLink extends React.Component {
constructor(props) {
super(props)
}
handleClick() {
this.props.handleClick(this.props.router)
}
render() {
const active = this.props.activeRouter === this.props.router
return (


  • style={{ color: active ? 'red' : 'black' }}
    onClick={this.handleClick.bind(this)}
    >
    Stateless Link
    </a>
    li>
    )
    }
    }

    React的实际开发当中,我们编写的组件大部分都是无状态组件。毕竟React的主要作用是编写用户界面。再加上ES6的新特性,绝大多数的无状态组件都可以通过箭头函数简写成类似下面这样

    1
    const SimpleButton = props => 

    4.4 受控与非受控组件

    受控组件

    比如说设置了value 是一个受控组件。对于受控的,渲染出来的html元素始终保持着value属性的值,如以下代码

    image

    • 此时如果想要更新用户的值。需要使用onChange事件

    image

    非受控组件

    即没有设置value或者设置为null的是一个非受控组件,对于非受控的input组件,用户的输入会直接反映在页面上

    image

    • 上面的代码渲染出一个空值的输入框,用户的输入立即会反映在元素上
    • 和受控组件一样,使用onChange事件来监听值的变化,如果想要给组件设置一个非空的初始值。可以使用defaultValue
    • 通常情况下,React当中所有的表单控件都需要是受控组件

    4.5 组合与继承

    • React当中的组件是通过嵌套或组合的方式实现组件代码复用的
    • 通过props传值和组合使用组件几乎可以满足所有场景下的需求。这样也更符合组件化的理念,就好像使用互相嵌套的dom元素一样使用React的组件,并不需要引入继承的概念

    继承的写法并不符合React的理念。在React当中props其实是非常强大的,props几乎可以传入任何东西,变量、函数、甚至是组件本身

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function SplitPane(props) {
    return (
    "SplitPane"
    >
    "SplitPane-left"
    >
    {props.left}
    </div>

    {props.right}
    div>
    </div>
    )
    }

    function App() {
    return (

    left={
    >
    }
    right={

    } />
    )
    }

    React官方也希望我们通过组合的方式来使用组件,如果你想实现一些非界面类型函数的复用,可以单独写在其他的模块当中在引入组件进行使用

    五、组件数据


    5.1 props

    • 传入变量
    • 传入函数
    • 传入组件
    • props.children
    • 在形式上,props之于JSX就相当于attributes之于HTML。从写法上来看呢,我们为组件传入props就可以像为HTML标签添加属性一样
    • 在概念上,props对于组件就相当于JS中参数之于函数。我们可以抽象出这样一个函数来解释
    • props 几乎可以传递所有的内容,包括变量、函数、甚至是组件本身

    props是只读的

    • React中,props都是自上向下传递,从父组件传入子组件
    • 并且props是只读的,我们不能在组件中直接修改props的内容
    • 也即是说组件只能根据传入的props渲染界面,而不能在其内部对props进行修改

    props类型检查

    正是因为props的强大,什么类型的内容都可以传递,所以在开发过程中,为了避免错误类型的内容传入,我们可以为props添加类型检查

    props默认值

    由于props是只读的,我们不能直接为props赋值。React专门准备了一个方法定义props的默认值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import React from 'react'
    import PropTypes from 'prop-types'

    const Title = props =>

    {props.title}</h1>



    Title.defaultProps = {
    title: 'Wait for parent to pass props.'
    }

    Title.propTypes = {
    title: PropTypes.string.isRequired
    }

    5.2 state

    • 初始化
    • setState方法
    • 向下传递数据
    • Reactstate也是我们进行数据交互的地方,又或者叫做state management状态管理。
    • 一个应用需要进行数据交互,比如同服务器之间的交互,同用户输入进行交互。话反过来,从api获取数据,处理用户输入也就是我们需要用到state的时候
    • 在新版本的React当中,我们通过类定义组件来声明一个有状态组件,之后在它的构造方法中初始化组件的state,我们可以先赋予它默认值。
    • 之后就可以在组件中通过this.state来访问它,既然是state那么肯定涉及到数据的改变,因此我们还需额外定义一个负责处理state变化的函数,这样的函数中一般都会包含this.setState这个方法
    • 和之前的props一样,初始化state之后,如果我们想改变它,是不可以直接对其赋值的,直接修改state的值没有任何意义,因为这样的操作脱离了React运行的逻辑,不会触发组件的重新渲染。所以需要this.setState这个方法,在改变state的同时,触发React内部的一系列函数,最后在页面上重新渲染出组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Counter extends React.Component {
    constructor(props) {
    super(props)
    this.state = {
    counter: 0
    }
    }

    addOne() {
    this.setState((prevState) =>({
    counter: prevState.counter + 1
    }))
    }

    render() {
    return (

    { this.state.counter }</p>



    onClick={() => this.addOne()}>
    Increment
    button>
    </div>
    )
    }
    }

    六、组件生命周期

    6.1 React是如何渲染组件的

    • 在新版本的React当中,React的底层被重写了。React换上了一个新的引擎,这个引擎叫做React Fiber.React Fiber 作用的也即是React最核心的功能,它将React应用界面更新的过程分为了两个主要的部分:
    • 调度过程
    • 执行过程

    在调度过程中,有4个生命周期函数会被触发

    • componentWillMount
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate

    在执行过程中,有3个生命周期函数会被触发:

    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount

    6.2 React组件生命周期方法

    React为了方便我们更好地控制自己的应用,提供了许多预置的生命周期方法。这些固定的生命周期方法分别会在组件的挂载流程、更新流程、卸载流程中触发

    • componentWillMount 开始插入真实dom
    • componentDidMount 插入真实dom完成
    • componentWillUpdate 开始重新渲染
    • componentDidUpdate 重新渲染完成
    • componentWillUnmount已移出真实 dom
    • componentWillReceiveProps 已加载组件收到新的参数时调用
    • shouldComponentUpdate组件判断是否重新渲染时调用

    image.png

    image.png

    componentDidMount

    在此方法中可进行

    • 与其他 javascript 框架集成,如初始化 jquery 插件;
    • 使用 setTimeout/setInterval 设置定时器;
    • 通过 Ajax/Fetch 获取数据;
    • 绑定 dom 事件

    6.3 总结

    • react组件渲染包含三个流程:挂载流程、更新流程、卸载流程
    • 各个生命周期函数会在特定的时刻触发并适用于不同的使用场景
    • 通过使用生命周期函数我们可以对应用进行更精准的控制
    • 如果你需要发起网络请求,将其安排在合适的生命周期函数中是值得推荐的做法
    • 了解掌握React组件渲染的流程和原理对我们更深入掌握React非常有帮助

    七、表单及事件处理

    7.1 表单

    受控与非受控组件就是专门适用于React当中的表单元素的

    • 只要是有表单出现的地方,就会有用户输入,就会有表单事件触发,就会涉及的数据处理
    • 在我们用React开发应用时,为了更好地管理应用中的数据,响应用户的输入,编写组件的时候呢,我们就会运用到受控组件与非受控组件这两个概念。

    7.2 表单元素

    我们在组件中声明表单元素时,一般都要为表单元素传入应用状态中的值,可以通过state也可以通过props传递,之后需要为其绑定相关事件,例如表单提交,输入改变等。在相关事件触发的处理函数中,我们需要根据表单元素中用户的输入,对应用数据进行相应的操作和改变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class ControlledInput extends React.Component {
    constructor(props) {
    super(props)
    this.state = {
    value: ""
    }
    }

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

    render() {
    return <input
    type="text"
    value={this.state.value}
    onChange={() => this.handleChange()}
    />
    }
    }

    受控组件的输入数据是一直和我们的应用状态绑定的,事件处理函数中一定要有关state的更新操作,这样表单组件才能及时正确响应用户的输入

    textarea

    1
    2
    3
    4
    5
    6
    7

    <textarea>
    Hello there, this is some text in a text area
    textarea>


    <textarea value={this.state.value} onChange={this.handleChange} />

    select

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    <select>
    <option value="grapefruit">Grapefruitoption>
    <option value="lime">Limeoption>
    <option selected value="coconut">Coconutoption>
    <option value="mango">Mangooption>
    select>


    <select value={this.state.value} onChange={this.handleChange}>
    <option value="grapefruit">Grapefruitoption>
    <option value="lime">Limeoption>
    <option value="coconut">Coconutoption>
    <option value="mango">Mangooption>
    select>

    7.3 事件

    1
    2
    3
    4
    5
    6
    7
    8
    9

    <button onclick="activateLasers()">
    Activate Lasers
    button>


    <button onClick={activateLasers}>
    Activate Lasers
    button>

    八、redux-router

    react-router

    8.1、基本用法

    使用时,路由器Router就是React的一个组件

    1
    2
    import { Router } from 'react-router';
    render(<Router/>, document.getElementById('app'));

    Router组件本身只是一个容器,真正的路由要通过Route组件定义

    1
    2
    3
    4
    5
    6
    7
    import { Router, Route, hashHistory } from 'react-router';

    render((

    "/" component={App}/>
    </Router>
    ), document.getElementById('app'));

    上面代码中,用户访问根路由/,组件APP就会加载到document.getElementById('app')

    • Router组件有一个参数history,它的值hashHistory表示,路由的切换由URLhash变化决定,即URL#部分发生变化
    • Route组件定义了URL路径与组件的对应关系。你可以同时使用多个Route组件
    1
    2
    3
    4
    5

    "/" component={App}/>
    "/repos" component={Repos}/>
    "/about" component={About}/>
    </Router>

    上面代码中,用户访问/repos(比如http://localhost:8080/#/repos)时,加载Repos组件;访问/about(http://localhost:8080/#/about)时,加载About组件

    8.2、嵌套路由

    Route组件还可以嵌套

    1
    2
    3
    4
    5
    6

    "/" component={App}>
    "/repos" component={Repos}/>
    "/about" component={About}/>
    </Route>
    Router>

    上面代码中,用户访问/repos时,会先加载App组件,然后在它的内部再加载Repos组件

    1
    2
    3


    </App>
    • App组件要写成下面的样子
    1
    2
    3
    4
    5
    6
    7
    export default React.createClass({
    render() {
    return <div>
    {this.props.children}
    div>
    }
    })

    App组件的this.props.children属性就是子组件

    8.3、 path 属性

    Route组件的path属性指定路由的匹配规则。这个属性是可以省略的,这样的话,不管路径是否匹配,总是会加载指定组件

    • Route组件的path属性指定路由的匹配规则。这个属性是可以省略的,这样的话,不管路径是否匹配,总是会加载指定组件
    1
    2
    3
    "inbox" component={Inbox}>
    "messages/:id" component={Message} />
    </Route>

    当用户访问/inbox/messages/:id时,会加载下面的组件

    1
    2
    3


    </Inbox>

    如果省略外层Routepath参数,写成下面的样子

    1
    2
    3

    "inbox/messages/:id" component={Message} />
    </Route>

    现在用户访问/inbox/messages/:id时,组件加载还是原来的样子

    1
    2
    3


    </Inbox>

    8.4、通配符

    path属性可以使用通配符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    "/hello/:name">
    // 匹配 /hello/michael
    // 匹配 /hello/ryan

    "/hello(/:name)">
    // 匹配 /hello
    // 匹配 /hello/michael
    // 匹配 /hello/ryan

    "/files/*.*">
    // 匹配 /files/hello.jpg
    // 匹配 /files/hello.html

    "/files/*">
    // 匹配 /files/
    // 匹配 /files/a
    // 匹配 /files/a/b

    "/**/*.jpg">
    // 匹配 /files/hello.jpg
    // 匹配 /files/path/to/file.jpg

    通配符的规则如下

    • :paramName

    :paramName匹配URL的一个部分,直到遇到下一个/?#为止。这个路径参数可以通过this.props.params.paramName取出

    • ()

    ()表示URL的这个部分是可选的

    • 匹配任意字符,直到模式里面的下一个字符为止。匹配方式是非贪婪模式
    • 匹配任意字符,直到下一个/?#为止。匹配方式是贪婪模式

    path属性也可以使用相对路径(不以/开头),匹配时就会相对于父组件的路径。嵌套路由如果想摆脱这个规则,可以使用绝对路由

    • 此外,URL的查询字符串/foo?bar=baz,可以用this.props.location.query.bar获取

    8.5、IndexRoute 组件

    1
    2
    3
    4
    5
    6

    "/" component={App}>
    "accounts" component={Accounts}/>
    "statements" component={Statements}/>
    </Route>
    Router>
    • 上面代码中,访问根路径/,不会加载任何子组件。也就是说,App组件的this.props.children,这时是undefined
    • 因此,通常会采用{this.props.children || }这样的写法。这时,Home明明是AccountsStatements的同级组件,却没有写在Route
    • IndexRoute就是解决这个问题,显式指定Home是根路由的子组件,即指定默认情况下加载的子组件。你可以把IndexRoute想象成某个路径的index.html
    1
    2
    3
    4
    5
    6
    7

    "/" component={App}>

    "accounts" component={Accounts}/>
    "statements" component={Statements}/>
    </Route>
    Router>

    现在,用户访问/的时候,加载的组件结构如下

    1
    2
    3


    </App>
    • 注意IndexRoute组件没有路径参数path

    8.6、Redirect 组件

    组件用于路由的跳转,即用户访问一个路由,会自动跳转到另一个路由

    1
    2
    3
    4
    "inbox" component={Inbox}>
    {/* 从 /inbox/messages/:id 跳转到 /messages/:id */}
    <Redirect from="messages/:id" to="/messages/:id" />
    </Route>

    现在访问/inbox/messages/5,会自动跳转到/messages/5

    8.7、IndexRedirect 组件

    IndexRedirect组件用于访问根路由的时候,将用户重定向到某个子组件

    1
    2
    3
    4
    5
    "/" component={App}>
    <IndexRedirect to="/welcome" />
    "welcome" component={Welcome} />
    "about" component={About} />
    </Route>

    用户访问根路径时,将自动重定向到子组件welcome

    Link组件用于取代元素,生成一个链接,允许用户点击后跳转到另一个路由。它基本上就是元素的React 版本,可以接收Router的状态

    1
    2
    3
    4
    5
    6
    7
    8
    render() {
    return <div>

    • About

    • Repos

    • ul>
      div>
      }

      如果希望当前的路由与其他路由有不同样式,这时可以使用Link组件的activeStyle属性

      1
      2
      "/about" activeStyle={{color: 'red'}}>About</Link>
      " activeStyle={{color: 'red'}}>Repos
      • Router组件之外,导航到路由页面,可以使用浏览器的History api,像下面这样写
      1
      2
      import { browserHistory } from 'react-router';
      browserHistory.push('/some/path');

      如果链接到根路由/,不要使用Link组件,而要使用IndexLink组件

      • 是因为对于根路由来说,activeStyleactiveClassName会失效,或者说总是生效,因为/会匹配任何子路由。而IndexLink组件会使用路径的精确匹配
      1
      2
      3
      "/" activeClassName="active">
      Home
      </IndexLink>

      上面代码中,根路由只会在精确匹配时,才具有activeClassName

      8.10、histroy 属性

      Router组件的history属性,用来监听浏览器地址栏的变化,并将URL解析成一个地址对象,供 React Router 匹配

      • history属性,一共可以设置三种值。
        • browserHistory
        • hashHistory
        • createMemoryHistory

      如果设为hashHistory,路由将通过URL的hash部分(#)切换,URL的形式类似example.com/#/some/path

      1
      2
      3
      4
      5
      6
      import { hashHistory } from 'react-router'

      render(
      ,
      document.getElementById('app')
      )

      如果设为browserHistory,浏览器的路由就不再通过Hash完成了,而显示正常的路径example.com/some/path,背后调用的是浏览器的History api

      1
      2
      3
      4
      5
      6
      import { browserHistory } from 'react-router'

      render(
      ,
      document.getElementById('app')
      )

      但是,这种情况需要对服务器改造。否则用户直接向服务器请求某个子路由,会显示网页找不到的404错误。

      8.11、表单处理

      Link组件用于正常的用户点击跳转,但是有时还需要表单跳转、点击按钮跳转等操作

      1
      2
      3
      4
      5
      this
      .handleSubmit}>
      "text" placeholder="userName"/>
      "text" placeholder="repo"/>
      >Go</button>
      form>

      第一种方法是使用browserHistory.push

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      import { browserHistory } from 'react-router'

      // ...
      handleSubmit(event) {
      event.preventDefault()
      const userName = event.target.elements[0].value
      const repo = event.target.elements[1].value
      const path = `/repos/${userName}/${repo}`
      browserHistory.push(path)
      },

      第二种方法是使用context对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      export default React.createClass({

      // ask for `router` from context
      contextTypes: {
      router: React.PropTypes.object
      },

      handleSubmit(event) {
      // ...
      this.context.router.push(path)
      },
      })

      8.12、路由的钩子

      每个路由都有EnterLeave钩子,用户进入或离开该路由时触发

      • 上面的代码中,如果用户离开/messages/:id,进入/about时,会依次触发以下的钩子
        • /messages/:idonLeave
        • /inboxonLeave
        • /aboutonEnter

      九、redux

      9.1 Redux 的适用场景

      • 某个组件的状态,需要共享
      • 某个状态需要在任何地方都可以拿到
      • 一个组件需要改变全局状态
      • 一个组件需要改变另一个组件的状态

      Redux设计思想

      Redux 的设计思想很简单,就两句话

      • Web 应用是一个状态机,视图与状态是一一对应的
      • 所有的状态,保存在一个对象里面

      9.2 基本概念和 api

      Store

      • Store 提供了三个方法
        • store.getState()
        • store.dispatch()
        • store.subscribe()
      1
      2
      import { createStore } from 'redux';
      let { subscribe, dispatch, getState } = createStore(reducer);

      Store就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store

      • Redux 提供createStore这个函数,用来生成 Store
      1
      2
      import { createStore } from 'redux';
      const store = createStore(reducer); // 返回新生成的 Store 对象

      State

      Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State

      • 当前时刻的 State,可以通过store.getState()拿到
      1
      2
      3
      4
      import { createStore } from 'redux';
      const store = createStore(reducer);

      const state = store.getState();

      Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然

      Action

      State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了

      • Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置
      1
      2
      3
      4
      const action = {
      type: 'ADD_TODO',
      payload: 'Learn Redux'
      }
      • 上面代码中,Action 的名称是ADD_TODO,它携带的信息是字符串Learn Redux
      • 可以这样理解,Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store

      action有两个作用,一个是定义我们的应用可以进行的动作或操作的类型,另一个是传递改变应用状态的数据。在Redux的约定中,action只有type属性是必须包含的,其他的数据如何定义全在于你想要如何使用,当然如果你希望你定义的action能够规范一些的话,也可以遵从Flux Standard Action的标准

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      {
      // action 类型
      type: 'INCREMENT',
      // payload 中返回我们要传递的数据,用来修改应用 state
      payload: {
      num: 1
      },
      // payload 数据未获取成功时返回 true
      error: false,
      // 一些不必要在 payload 中传递的其他数据
      meta: {
      success: true
      }
      }

      Action Creator

      View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const ADD_TODO = '添加 TODO';

      function addTodo(text) {
      return {
      type: ADD_TODO,
      text
      }
      }

      const action = addTodo('Learn Redux');

      上面代码中,addTodo函数就是一个 Action Creator

      store.dispatch()

      store.dispatch()View 发出 Action 的唯一方法

      1
      2
      3
      4
      5
      6
      7
      import { createStore } from 'redux';
      const store = createStore(fn);

      store.dispatch({
      type: 'ADD_TODO',
      payload: 'Learn Redux'
      });

      上面代码中,store.dispatch接受一个 Action 对象作为参数,将它发送出去

      • 结合 Action Creator,这段代码可以改写如下
      1
      store.dispatch(addTodo('Learn Redux'));

      Reducer

      Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer

      1
      2
      3
      4
      const reducer = function (state, action) {
      // ...
      return new_state;
      };
      • 整个应用的初始状态,可以作为 State 的默认值。下面是一个实际的例子
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      const defaultState = 0;
      const reducer = (state = defaultState, action) => {
      switch (action.type) {
      case 'ADD':
      return state + action.payload;
      default:
      return state;
      }
      };

      const state = reducer(1, {
      type: 'ADD',
      payload: 2
      });

      上面代码中,reducer函数收到名为ADDAction 以后,就返回一个新的 State,作为加法的计算结果。其他运算的逻辑(比如减法),也可以根据 Action 的不同来实现

      • 实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch方法会触发 Reducer 的自动执行
      • 为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法
      1
      2
      import { createStore } from 'redux';
      const store = createStore(reducer);
      • 上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State
      • 为什么这个函数叫做 Reducer呢?因为它可以作为数组的reduce方法的参数

      纯函数

      • Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出
      • 纯函数是函数式编程的概念,必须遵守以下一些约束
        • 不得改写参数
        • 不能调用系统 I/Oapi
        • 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

      由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // State 是一个对象
      function reducer(state, action) {
      return Object.assign({}, state, { thingToChange });
      // 或者
      return { ...state, ...newState };
      }

      // State 是一个数组
      function reducer(state, action) {
      return [...state, newItem];
      }

      最好把 State 对象设成只读。你没法改变它,要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变的对象

      store.subscribe()

      Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数

      1
      2
      3
      4
      import { createStore } from 'redux';
      const store = createStore(reducer);

      store.subscribe(listener);

      显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染

      • store.subscribe方法返回一个函数,调用这个函数就可以解除监听
      1
      2
      3
      4
      5
      let unsubscribe = store.subscribe(() =>
      console.log(store.getState())
      );

      unsubscribe();

      9.3 Reducer 的拆分

      Reducer 函数负责生成 State。由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      const chatReducer = (state = defaultState, action = {}) => {
      const { type, payload } = action;
      switch (type) {
      case ADD_CHAT:
      return Object.assign({}, state, {
      chatLog: state.chatLog.concat(payload)
      });
      case CHANGE_STATUS:
      return Object.assign({}, state, {
      statusMessage: payload
      });
      case CHANGE_USERNAME:
      return Object.assign({}, state, {
      userName: payload
      });
      default: return state;
      }
      };
      1
      2
      3
      4
      5
      6
      7
      const chatReducer = (state = defaultState, action = {}) => {
      return {
      chatLog: chatLog(state.chatLog, action),
      statusMessage: statusMessage(state.statusMessage, action),
      userName: userName(state.userName, action)
      }
      };
      • 上面代码中,Reducer 函数被拆成了三个小函数,每一个负责生成对应的属
      • 这样一拆,Reducer 就易读易写多了。而且,这种拆分与 React 应用的结构相吻合:一个 React 根组件由很多子组件构成。这就是说,子组件与子 Reducer 完全可以对应

      Redux 提供了一个combineReducers方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer

      1
      2
      3
      4
      5
      6
      7
      8
      9
      import { combineReducers } from 'redux';

      const chatReducer = combineReducers({
      chatLog,
      statusMessage,
      userName
      })

      export default todoApp;

      这种写法有一个前提,就是 State 的属性名必须与子 Reducer 同名。如果不同名,就要采用下面的写法

      1
      2
      3
      4
      5
      6
      7
      function reducer(state = {}, action) {
      return {
      a: doSomethingWithA(state.a, action),
      b: processB(state.b, action),
      c: c(state.c, action)
      }
      }

      总之,combineReducers()做的就是产生一个整体的 Reducer 函数。该函数根据 Statekey 去执行相应的子 Reducer,并将返回结果合并成一个大的 State 对象

      • 你可以把所有子 Reducer 放在一个文件里面,然后统一引入
      1
      2
      3
      4
      import { combineReducers } from 'redux'
      import * as reducers from './reducers'

      const reducer = combineReducers(reducers)

      9.4 工作流程

      image

      image.png

      react组件+redux单向数据流

      前端异步请求用例

      PFAT如何解决前端异步请求的用例?

      • 首先,用户发出 Action
      1
      store.dispatch(action);
      • 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 ActionReducer 会返回新的 State
      1
      let nextState = todoApp(previousState, action);
      • State 一旦有变化,Store 就会调用监听函数
      1
      2
      // 设置监听函数
      store.subscribe(listener);
      • listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View
      1
      2
      3
      4
      function listerner() {
      let newState = store.getState();
      component.setState(newState);
      }

      9.5 实例:计数器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      const Counter = ({ value, onIncrement, onDecrement }) => (

      {value}</h1>




      </div>
      );

      const reducer = (state = 0, action) => {
      switch (action.type) {
      case 'INCREMENT': return state + 1;
      case 'DECREMENT': return state - 1;
      default: return state;
      }
      };

      const store = createStore(reducer);

      const render = () => {
      Reactdom.render(

      value={store.getState()}
      onIncrement={() => store.dispatch({type: 'INCREMENT'})}
      onDecrement={() => store.dispatch({type: 'DECREMENT'})}
      />,
      document.getElementById('root')
      );
      };

      render();
      store.subscribe(render);

      十、中间件与异步操作

      Redux 的基本做法:用户发出 ActionReducer 函数算出新的 StateView 重新渲染

      • 一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步

      • 怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware

      image

      10.1 中间件的概念

      中间件就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

      10.2 中间件的用法

      常用的中间件都有现成的,只要引用别人写好的模块即可。比如日志中间件,就有现成的redux-logger模块

      1
      2
      3
      4
      5
      6
      7
      8
      import { applyMiddleware, createStore } from 'redux';
      import createLogger from 'redux-logger';
      const logger = createLogger();

      const store = createStore(
      reducer,
      applyMiddleware(logger)
      );

      上面代码中,redux-logger提供一个生成器createLogger,可以生成日志中间件logger。然后,将它放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强

      这里有两点需要注意

      • (1)createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了
      1
      2
      3
      4
      5
      const store = createStore(
      reducer,
      initial_state,
      applyMiddleware(logger)
      );
      • (2)中间件的次序有讲究
      1
      2
      3
      4
      const store = createStore(
      reducer,
      applyMiddleware(thunk, promise, logger)
      );

      上面代码中,applyMiddleware方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger就一定要放在最后,否则输出结果会不正确

      10.3、applyMiddlewares()

      applyMiddlewares这个方法。它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行

      10.4 异步操作的基本思路

      理解了中间件以后,就可以处理异步操作了

      • 同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action
        • 操作发起时的 Action
        • 操作成功时的 Action
        • 操作失败时的 Action

      以向服务器取出数据为例,三种 Action 可以有两种不同的写法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 写法一:名称相同,参数不同
      { type: 'FETCH_POSTS' }
      { type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
      { type: 'FETCH_POSTS', status: 'success', response: { ... } }

      // 写法二:名称不同
      { type: 'FETCH_POSTS_REQUEST' }
      { type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
      { type: 'FETCH_POSTS_SUCCESS', response: { ... } }

      除了 Action种类不同,异步操作的 State 也要进行改造,反映不同的操作状态。下面是 State 的一个例子

      1
      2
      3
      4
      5
      6
      let state = {
      // ...
      isFetching: true,
      didInvalidate: true,
      lastUpdated: 'xxxxxxx'
      };

      上面代码中,State 的属性isFetching表示是否在抓取数据。didInvalidate表示数据是否过时,lastUpdated表示上一次更新时间

      现在,整个异步操作的思路就很清楚了

      • 操作开始时,送出一个 Action,触发 State 更新为”正在操作”状态,View 重新渲染
      • 操作结束后,再送出一个 Action,触发 State 更新为”操作结束”状态,View 再一次重新渲染

      10.5 redux-thunk 中间件

      异步操作至少要送出两个 Action:用户触发第一个 Action,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action

      • 奥妙就在 Action Creator 之中
      1
      2
      3
      4
      5
      6
      7
      class AsyncApp extends Component {
      componentDidMount() {
      const { dispatch, selectedPost } = this.props
      dispatch(fetchPosts(selectedPost))
      }

      // ...

      上面代码是一个异步组件的例子。加载成功后(componentDidMount方法),它送出了(dispatch方法)一个 Action,向服务器要求数据 fetchPosts(selectedSubreddit)。这里的fetchPosts就是 Action Creator

      • 下面就是fetchPosts的代码,关键之处就在里面

      image

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      const fetchPosts = postTitle => (dispatch, getState) => {
      dispatch(requestPosts(postTitle));
      return fetch(`/some/api/${postTitle}.json`)
      .then(response => response.json())
      .then(json => dispatch(receivePosts(postTitle, json)));
      };
      };

      // 使用方法一
      store.dispatch(fetchPosts('reactjs'));
      // 使用方法二
      store.dispatch(fetchPosts('reactjs')).then(() =>
      console.log(store.getState())
      );

      上面代码中,fetchPosts是一个Action Creator(动作生成器),返回一个函数。这个函数执行后,先发出一个Action(requestPosts(postTitle)),然后进行异步操作。拿到结果后,先将结果转成 JSON 格式,然后再发出一个 Action( receivePosts(postTitle, json)

      上面代码中,有几个地方需要注意

      • fetchPosts返回了一个函数,而普通的 Action Creator 默认返回一个对象
      • 返回的函数的参数是dispatchgetState这两个 Redux方法,普通的Action Creator的参数是 Action 的内容
      • 在返回的函数之中,先发出一个 Action(requestPosts(postTitle)),表示操作开始
      • 异步操作结束之后,再发出一个 Action(receivePosts(postTitle, json)),表示操作结束

      这样的处理,就解决了自动发送第二个 Action 的问题。但是,又带来了一个新的问题,Action 是由store.dispatch方法发送的。而store.dispatch方法正常情况下,参数只能是对象,不能是函数

      • 这时,就要使用中间件redux-thunk
      1
      2
      3
      4
      5
      6
      7
      8
      9
      import { createStore, applyMiddleware } from 'redux';
      import thunk from 'redux-thunk';
      import reducer from './reducers';

      // Note: this api requires redux@>=3.1.0
      const store = createStore(
      reducer,
      applyMiddleware(thunk)
      );
      • 上面代码使用redux-thunk中间件,改造store.dispatch,使得后者可以接受函数作为参数

      因此,异步操作的第一种解决方案就是,写出一个返回函数的 Action Creator,然后使用redux-thunk中间件改造store.dispatch

      10.6、redux-promise 中间件


      既然 Action Creator 可以返回函数,当然也可以返回其他值。另一种异步操作的解决方案,就是让 Action Creator 返回一个 Promise 对象

      • 这就需要使用redux-promise中间件
      1
      2
      3
      4
      5
      6
      7
      8
      import { createStore, applyMiddleware } from 'redux';
      import promiseMiddleware from 'redux-promise';
      import reducer from './reducers';

      const store = createStore(
      reducer,
      applyMiddleware(promiseMiddleware)
      );

      这个中间件使得store.dispatch方法可以接受 Promise 对象作为参数。这时,Action Creator 有两种写法

      • 写法一,返回值是一个 Promise 对象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      const fetchPosts = 
      (dispatch, postTitle) => new Promise(function (resolve, reject) {
      dispatch(requestPosts(postTitle));
      return fetch(`/some/api/${postTitle}.json`)
      .then(response => {
      type: 'FETCH_POSTS',
      payload: response.json()
      });
      });
      • 写法二,Action 对象的payload属性是一个 Promise 对象。这需要从redux-actions模块引入createAction方法,并且写法也要变成下面这样
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import { createAction } from 'redux-actions';

      class AsyncApp extends Component {
      componentDidMount() {
      const { dispatch, selectedPost } = this.props
      // 发出同步 Action
      dispatch(requestPosts(selectedPost));
      // 发出异步 Action
      dispatch(createAction(
      'FETCH_POSTS',
      fetch(`/some/api/${postTitle}.json`)
      .then(response => response.json())
      ));
      }
      • 上面代码中,第二个dispatch方法发出的是异步 Action,只有等到操作结束,这个 Action 才会实际发出
      • 注意,createAction的第二个参数必须是一个 Promise 对象

      十一、react-redux

      • 为了方便使用,Redux 的作者封装了一个 React专用的库 React-Redux
      • 这个库是可以选用的。实际项目中,你应该权衡一下,是直接使用 Redux,还是使用 React-Redux。后者虽然提供了便利,但是需要掌握额外的 api,并且要遵守它的组件拆分规范

      11.1 UI 组件

      React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component

      UI 组件有以下几个特征

      • 只负责 UI 的呈现,不带有任何业务逻辑
      • 没有状态(即不使用this.state这个变量)
      • 所有数据都由参数(this.props)提供
      • 不使用任何 Reduxapi
      1
      2
      3
      // 例子
      const Title =
      value => <h1>{value}h1>;

      因为不含有状态,UI 组件又称为”纯组件”,即它纯函数一样,纯粹由参数决定它的值

      11.2、容器组件

      • 负责管理数据和业务逻辑,不负责 UI 的呈现
      • 带有内部状态
      • 使用 Reduxapi

      UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑

      如果一个组件既有 UI 又有业务逻辑,那怎么办?回答是,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图

      • React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它

      11.3、connect()

      React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。

      1
      2
      import { connect } from 'react-redux'
      const VisibleTodoList = connect()(TodoList);
      • 上面代码中,TodoListUI 组件,VisibleTodoList就是由 React-Redux 通过connect方法自动生成的容器组件

      但是,因为没有定义业务逻辑,上面这个容器组件毫无意义,只是 UI 组件的一个单纯的包装层。为了定义业务逻辑,需要给出下面两方面的信息。

      • (1)输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数
      • (2)输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去

      因此,connect方法的完整 api 如下

      1
      2
      3
      4
      5
      6
      import { connect } from 'react-redux'

      const VisibleTodoList = connect(
      mapStateToProps,
      mapDispatchToProps
      )(TodoList)

      上面代码中,connect方法接受两个参数:mapStateToPropsmapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action

      11.4、mapStateToProps()

      mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系

      • 作为函数,mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射
      1
      2
      3
      4
      5
      const mapStateToProps = (state) => {
      return {
      todos: getVisibleTodos(state.todos, state.visibilityFilter)
      }
      }
      • 上面代码中,mapStateToProps是一个函数,它接受state作为参数,返回一个对象
      • 这个对象有一个todos属性,代表 UI 组件的同名参数,后面的getVisibleTodos也是一个函数,可以从state算出 todos 的值

      下面就是getVisibleTodos的一个例子,用来算出todos

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      const getVisibleTodos = (todos, filter) => {
      switch (filter) {
      case 'SHOW_ALL':
      return todos
      case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
      case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
      default:
      throw new Error('Unknown filter: ' + filter)
      }
      }
      • mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染
      • mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 容器组件的代码
      //
      // All
      //

      const mapStateToProps = (state, ownProps) => {
      return {
      active: ownProps.filter === state.visibilityFilter
      }
      }

      使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染

      • connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新

      11.5、mapDispatchToProps()

      mapDispatchToPropsconnect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射

      • 也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象
      • 如果mapDispatchToProps是一个函数,会得到dispatchownProps(容器组件的props对象)两个参数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      const mapDispatchToProps = (
      dispatch,
      ownProps
      ) => {
      return {
      onClick: () => {
      dispatch({
      type: 'SET_VISIBILITY_FILTER',
      filter: ownProps.filter
      });
      }
      };
      }
      • 从上面代码可以看到,mapDispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action
      • 如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。举例来说,上面的mapDispatchToProps写成对象就是下面这样
      1
      2
      3
      4
      5
      6
      const mapDispatchToProps = {
      onClick: (filter) => {
      type: 'SET_VISIBILITY_FILTER',
      filter: filter
      };
      }

      11.6、 组件

      connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数

      一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。

      • React-Redux 提供Provider组件,可以让容器组件拿到state
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      import { Provider } from 'react-redux'
      import { createStore } from 'redux'
      import todoApp from './reducers'
      import App from './components/App'

      let store = createStore(todoApp);

      render(


      </Provider>,
      document.getElementById('root')
      )
      • 上面代码中,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state
      • 它的原理是React组件的context属性

      11.7、实例:计数器

      我们来看一个实例。下面是一个计数器组件,它是一个纯的 UI 组件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      class Counter extends Component {
      render() {
      const { value, onIncreaseClick } = this.props
      return (

      {value}</span>

      </div>
      )
      }
      }

      这个 UI 组件有两个参数:valueonIncreaseClick。前者需要从state计算得到,后者需要向外发出 Action

      • 接着,定义valuestate的映射,以及onIncreaseClickdispatch的映射
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      function mapStateToProps(state) {
      return {
      value: state.count
      }
      }

      function mapDispatchToProps(dispatch) {
      return {
      onIncreaseClick: () => dispatch(increaseAction)
      }
      }

      // Action Creator
      const increaseAction = { type: 'increase' }

      然后,使用connect方法生成容器组件

      1
      2
      3
      4
      const App = connect(
      mapStateToProps,
      mapDispatchToProps
      )(Counter)

      然后,定义这个组件的 Reducer

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // Reducer
      function counter(state = { count: 0 }, action) {
      const count = state.count
      switch (action.type) {
      case 'increase':
      return { count: count + 1 }
      default:
      return state
      }
      }

      最后,生成store对象,并使用Provider在根组件外面包一层

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      import React, { Component } from 'react'
      import PropTypes from 'prop-types'
      import Reactdom from 'react-dom'
      import { createStore } from 'redux'
      import { Provider, connect } from 'react-redux'

      // React component
      class Counter extends Component {
      render() {
      const { value, onIncreaseClick } = this.props
      return (

      {value}</span>

      </div>
      )
      }
      }

      Counter.propTypes = {
      value: PropTypes.number.isRequired,
      onIncreaseClick: PropTypes.func.isRequired
      }

      // Action
      const increaseAction = { type: 'increase' }

      // Reducer
      function counter(state = { count: 0 }, action) {
      const count = state.count
      switch (action.type) {
      case 'increase':
      return { count: count + 1 }
      default:
      return state
      }
      }

      // Store
      const store = createStore(counter)

      // Map Redux state to component props
      function mapStateToProps(state) {
      return {
      value: state.count
      }
      }

      // Map Redux actions to component props
      function mapDispatchToProps(dispatch) {
      return {
      onIncreaseClick: () => dispatch(increaseAction)
      }
      }

      // Connected Component
      const App = connect(
      mapStateToProps,
      mapDispatchToProps
      )(Counter)

      Reactdom.render(

      >
      </Provider>,
      document.getElementById('root')
      )

      十二、思维导图总结

      image.png

      转载请保持原始链接

      原始链接: https://ru23.com/note/eb2df852.html