react 分享

547 阅读39分钟

react

创建项目

    npm install -g create-react-app 
    npx create-react-app react-demo
  • 注意,一般旧版的create-react-app创建项目成功是没有提供模版,需要卸载旧版的create-react-app,下载新版本的create-react-app
    npm uninstall -g create-react-app
    // 就是卸载失败,到该文件定向删除
    请运行rm -rf /usr/local/bin/create-react-app

webpack 配置

  • 用脚手架create-react-app 创建项目的时候,一般是没有暴露webpack配置文件,都是默认配置,需要增加配置项,需要暴露配置文件,需要运行 npm run eject 指令,这样就可以暴露配置项,但是不建议这么做,不要和默认配置混合,不利于排查问题

  • 需要增加webpack配置项,新建config-overrides.js文件,antd的按需加载,less的配置
const { override, fixBabelImports, addLessLoader, addWebpackAlias } = require('customize-cra');
const path = require('path');
function resolve(dir) {
    return path.join(__dirname, '.', dir)
}
 
module.exports = override(
    // antd按需加载,不需要每个页面都引入“antd/dist/antd.css”了
    fixBabelImports('import', {
      libraryName: 'antd',
      libraryDirectory: 'es',
      style: true  //这里一定要写true,css和less都不行
    }),
    // 配置路径别名
    addWebpackAlias({
      '@': resolve("src")
    }),
    addLessLoader({
      javascriptEnabled: true,
      //下面这行很特殊,这里是更改主题的关键,这里我只更改了主色,当然还可以更改其他的,下面会详细写出。
      modifyVars: { "@primary-color": "red"}
    })
)
  • proxy的配置,代理ip
    • 安装 http-proxy-middleware
    yarn add http-proxy-middleware --dev
    
    • 在src目录下新建setupProxy.js,并且写入
    const proxy = require("http-proxy-middleware");
    module.exports = function(app) {
      // app.use(
      //   proxy.createProxyMiddleware("http://localhost:3000", {
      //     target: "http://baidu.com/",
      //     changeOrigin: true
      //   })
      // );
      app.use(
        proxy.createProxyMiddleware("/fans/**", {
          target: "https://easy-mock.com/mock/5c0f31837214cf627b8d43f0/",
          changeOrigin: true
        })
      );
    };
    

react基本概念

  • react 背景介绍:React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
  • 什么是react:
    • 用来构建UI的 JavaScript库
    • React 不是一个 MVC 框架,仅仅是视图(V)层的库
    • MVC:MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式
      • Model(模型)表示应用程序核心(比如数据库记录列表)
      • View(视图)显示数据(数据库记录)
      • Controller(控制器)处理输入(写入数据库记录)
  • 特点:
    • 使用 JSX语法 创建组件,实现组件化开发,为函数式的 UI 编程方式打开了大门
    • 性能高的让人称赞:通过 diff算法 和 虚拟DOM 实现视图的高效更新
  • 为什么要用react:
    • 使用组件化开发方式,符合现代Web开发的趋势
    • 技术成熟,社区完善,配件齐全,适用于大型Web项目(生态系统健全)
    • 由Facebook专门的团队维护,技术支持可靠
    • 使用方式简单,性能非常高,支持服务端渲染
    • React非常火,从技术角度,可以满足好奇心,提高技术水平;从职业角度,有利于求职和晋升,有利于参与潜力大的项
  • react中的核心:
    • 虚拟DOM(Virtual DOM)
    • Diff算法(虚拟DOM的加速器,提升React性能的法宝)
  • 虚拟Dom(Virtual DOM):
    • React将DOM抽象为虚拟DOM,虚拟DOM其实就是用一个对象来描述DOM,通过对比前后两个对象的差异,最终只把变化的部分重新渲染,提高渲染的效率
    • 为什么用虚拟dom,当dom反生更改时需要遍历 而原生dom可遍历属性多大231个 且大部分与渲染无关 更新页面代价太大
  • VituralDOM的处理方式:
    • 用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树,插到文档当中
    • 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
    • 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了
  • Diff算法:
    • 当你使用React的时候,在某个时间点 render() 函数创建了一棵React元素树,在下一个state或者props更新的时候,render() 函数将创建一棵新的React元素树,React将对比这两棵树的不同之处,计算出如何高效的更新UI(只更新变化的地方)
    • 有一些解决将一棵树转换为另一棵树的最小操作数算法问题的通用方案。然而,树中元素个数为n,最先进的算法 的时间复杂度为O(n3) 。
    • 如果直接使用这个算法,在React中展示1000个元素则需要进行10亿次的比较。这操作太过昂贵,相反,React基于两点假设,实现了一个O(n)算法,提升性能
    • React中有两种假定:
      • 两个不同类型的元素会产生不同的树(根元素不同结构树一定不同)
      • 开发者可以通过key属性指定不同树中没有发生改变的子元素
    • Diff算法的说明 - 1: 如果两棵树的根元素类型不同,React会销毁旧树,创建新树
          // 旧树
          <div>
            <Counter />
          </div>
          
          // 新树
          <span>
            <Counter />
          </span>
          
          执行过程:destory Counter -> insert Counter
      
      
    • Diff算法的说明 - 2
      • 对于类型相同的React DOM元素,React会对比两者的属性是否相同,只更新不同的属性
      • 当处理完这个DOM节点,React就会递归处理子节点。
            // 旧
            <div className="before" title="stuff" />
            // 新
            <div className="after" title="stuff" />
            只更新:className 属性
            
            // 旧
            <div style={{color: 'red', fontWeight: 'bold'}} />
            // 新
            <div style={{color: 'green', fontWeight: 'bold'}} />
            只更新:color属性
        
        
    • Diff算法的说明 - 3
      • 当在子节点的后面添加一个节点,这时候两棵树的转化工作执行的很好
            // 旧
            <ul>
              <li>first</li>
              <li>second</li>
            </ul>
            
            // 新
            <ul>
              <li>first</li>
              <li>second</li>
              <li>third</li>
            </ul>
            
            执行过程:
            React会匹配新旧两个<li>first</li>,匹配两个<li>second</li>,然后添加 <li>third</li> tree
        
      • 但是如果你在开始位置插入一个元素,那么问题就来了:
            // 旧
            <ul>
              <li>Duke</li>
              <li>Villanova</li>
            </ul>
            
            // 新
            <ul>
              <li>Connecticut</li>
              <li>Duke</li>
              <li>Villanova</li>
            </ul>
            
            在没有key属性时执行过程:
            React将改变每一个子删除重新创建,而非保持 <li>Duke</li> 和 <li>Villanova</li> 不变
        
  • key属性: 为了解决以上问题,React提供了一个 key 属性。当子节点带有key属性,React会通过key来匹配原始树和后来的树。
        // 旧
        <ul>
          <li key="2015">Duke</li>
          <li key="2016">Villanova</li>
        </ul>
        
        // 新
        <ul>
          <li key="2014">Connecticut</li>
          <li key="2015">Duke</li>
          <li key="2016">Villanova</li>
        </ul>
        执行过程:
        现在 React 知道带有key '2014' 的元素是新的,对于 '2015''2016' 仅仅移动位置即可 
    
    • 说明:key属性在React内部使用,但不会传递给你的组件
    • 推荐:在遍历数据时,推荐在组件中使用 key 属性:
          <li key={item.id}>{item.name}</li>
      
    • 注意:key只需要保持与他的兄弟节点唯一即可,不需要全局唯一
    • 注意:尽可能的减少数组index作为key,数组中插入元素的等操作时,会使得效率底下
  • react的基本使用
    • 安装: npm i -S react react-dom
    • react:react 是React库的入口点
    • react-dom:提供了针对DOM的方法,比如:把创建的虚拟DOM,渲染到页面上
          // 1. 导入 react
          import React from 'react'
          import ReactDOM from 'react-dom'
          
          // 2. 创建 虚拟DOM
          // 参数1:元素名称  参数2:元素属性对象(null表示无)  参数3:当前元素的子元素string||createElement() 的返回值
          const divVD = React.createElement('div', {
            title: 'hello react'
          }, 'Hello React!!!')
          
          // 3. 渲染
          // 参数1:虚拟dom对象  参数2:dom对象表示渲染到哪个元素内 参数3:回调函数
          ReactDOM.render(divVD, document.getElementById('app'))
      
    • createElement()的问题 : createElement()方式,代码编写不友好,太复杂
      var dv = React.createElement(
        "div",
        { className: "shopping-list" },
        React.createElement(
          "h1",
          null,
          "Shopping List for "
        ),
        React.createElement(
          "ul",
          null,
          React.createElement(
            "li",
            null,
            "Instagram"
          ),
          React.createElement(
            "li",
            null,
            "WhatsApp"
          )
        )
      )
      // 渲染
      ReactDOM.render(dv, document.getElementById('app'))
      
    • jsx的基本使用
      • 注意:JSX语法,最终会被编译为 createElement() 方法
      • 推荐:使用 JSX 的方式创建组件
      • 安装:npm i -D babel-preset-react (依赖与:babel-core/babel-loader)
      • 注意:JSX的语法需要通过 babel-preset-react 编译后,才能被解析执行
            /* 1 在 .babelrc 开启babel对 JSX 的转换 */
            {
              "presets": [
                "env", "react"
              ]
            }
            
            /* 2 webpack.config.js */
            module: [
              rules: [
                { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ },
              ]
            ]
            
            /* 3 在 js 文件中 使用 JSX */
            const dv = (
              <div title="标题" className="cls container">Hello JSX!</div>
            )
            
            /* 4 渲染 JSX 到页面中 */
            ReactDOM.render(dv, document.getElementById('app'))
        
    • JSX的注意点
      • 如果在 JSX 中给元素添加类, 需要使用 className 代替 class
      • 类似:label 的 for属性,使用htmlFor代替
      • 在 JSX 中只能使用表达式,但是不能出现 语句!!!
      • 在 JSX 中注释语法:{/* 中间是注释的内容 */}
    • react组件:React 组件可以让你把UI分割为独立、可复用的片段,并将每一片段视为相互独立的部分
      • 组件是由一个个的HTML元素组成的
      • 概念上来讲, 组件就像JS中的函数。它们接受用户输入(props),并且返回一个React对象,用来描述展示在页面中的内容
    • React创建组件的两种方式
      • 通过 JS函数 创建(无状态组件)
      • 通过 class 创建(有状态组件)
      • 函数式组件 和 class 组件的使用场景说明:
        • 如果一个组件仅仅是为了展示数据,那么此时就可以使用 函数组件
        • 如果一个组件中有一定业务逻辑,需要操作数据,那么就需要使用 class 创建组件,因为,此时需要使用 state
        • Hook ....
    • JavaScript函数创建组件:
      • 注意:1 函数名称必须为大写字母开头,React通过这个特点来判断是不是一个组件
      • 注意:2 函数必须有返回值,返回值可以是:JSX对象或null
      • 注意:3 返回的JSX,必须有一个根元素
      • 注意:4 组件的返回值使用()包裹,避免换行问题
            function Welcome(props) {
              return (
                // 此处注释的写法 
                <div className="shopping-list">
                  {/* 此处 注释的写法 必须要{}包裹 */}
                  <h1>Shopping List for {props.name}</h1>
                  <ul>
                    <li>Instagram</li>
                    <li>WhatsApp</li>
                  </ul>
                </div>
              )
            }
            
            ReactDOM.render(
              <Welcome name="jack" />,
              document.getElementById('app')
            )
        
    • class创建组件 : 在es6中class仅仅是一个语法糖,不是真正的类,本质上还是构造函数+原型 实现继承
          // ES6中class关键字的简单使用
      
          // - **ES6中的所有的代码都是运行在严格模式中的**
          // - 1 它是用来定义类的,是ES6中实现面向对象编程的新方式
          // - 2 使用`static`关键字定义静态属性
          // - 3 使用`constructor`构造函数,创建实例属性
          // - [参考](http://es6.ruanyifeng.com/#docs/class)
          
          // 语法:
          class Person {
            // 实例的构造函数 constructor
            constructor(age){
              // 实例属性
              this.age = age
            }
            // 在class中定义方法 此处为实例方法 通过实例打点调用
            sayHello () {
              console.log('大家好,我今年' + this.age + '了');
            }
          
            // 静态方法 通过构造函数打点调用 Person.doudou()
            static doudou () {
              console.log('我是小明,我新get了一个技能,会暖床');
            }
          }
          // 添加静态属性
          Person.staticName = '静态属性'
          // 实例化对象
          const p = new Person(19)
           
           
          // 实现继承的方式
           
          class American extends Person {
            constructor() {
              // 必须调用super(), super表示父类的构造函数
              super()
              this.skin = 'white'
              this.eyeColor = 'white'
            }
          }
          
          // 创建react对象
          // 注意:基于 `ES6` 中的class,需要配合 `babel` 将代码转化为浏览器识别的ES5语法
          // 安装:`npm i -D babel-preset-env`
           
          //  react对象继承字React.Component
          class ShoppingList extends React.Component {
            constructor(props) { 
              super(props)
            }
            // class创建的组件中 必须有rander方法 且显示return一个react对象或者null
            render() {
              return (
                <div className="shopping-list">
                  <h1>Shopping List for {this.props.name}</h1>
                  <ul>
                    <li>Instagram</li>
                    <li>WhatsApp</li>
                  </ul>
                </div>
              )
            }
          }
      
    • 给组件传递数据 - 父子组件传递数据
      • 组件中有一个 只读的对象 叫做 props,无法给props添加属性
      • 获取方式:函数参数 props
      • 作用:将传递给组件的属性转化为 props 对象中的属性
      • 父组件-子组件: prop获取属性 子组件-父组件: props的方法?
        function Welcome(props){
          // props ---> { username: 'zs', age: 20 }
          return (
            <div>
              <div>Welcome React</div>
              <h3>姓名:{props.username}----年龄是:{props.age}</h3>
            </div>
          )
        }
        
        // 给 Hello组件 传递 props:username 和 age(如果你想要传递numb类型是数据 就需要向下面这样)
        ReactDOM.reander(<Hello username="zs" age={20}></Hello>, ......)
        
            // 创建Hello2.js组件文件
            // 1. 引入React模块
            // 由于 JSX 编译后会调用 React.createElement 方法,所以在你的 JSX 代码中必须首先拿到React。
            import React from 'react'
            
            // 2. 使用function构造函数创建组件
            function Hello2(props){
              return (
                <div>
                  <div>这是Hello2组件</div>
                  <h1>这是大大的H1标签,我大,我骄傲!!!</h1>
                  <h6>这是小小的h6标签,我小,我傲娇!!!</h6>
                </div>
              )
            }
            // 3. 导出组件
            export default Hello2
            
            // app.js中   使用组件:
            import Hello2 from './components/Hello2'
        
    • props和state
      • props:
        • 作用:给组件传递数据,一般用在父子组件之间
        • 说明:React把传递给组件的属性转化为一个对象并交给 props
        • 特点:props是只读的,无法给props添加或修改属性
              // props 是一个包含数据的对象参数,不要试图修改 props 参数
              // 返回值:react元素
              function Welcome(props) {
                // 返回的 react元素中必须只有一个根元素
                return <div>hello, {props.name}</div>
              }
              
              class Welcome extends React.Component {
                constructor(props) {
                  super(props)
                }
              
                render() {
                  return <h1>Hello, {this.props.name}</h1>
                }
              }
          
      • state : 状态即数据
        • 作用:用来给组件提供组件内部使用的数据
        • 注意:只有通过class创建的组件才具有状态
        • 状态是私有的,完全由组件来控制
        • 不要在 state 中添加 render() 方法中不需要的数据,会影响渲染性能!
          • 可以将组件内部使用但是不渲染在视图中的内容,直接添加给 this
        • 不要在 render() 方法中调用 setState() 方法来修改state的值
          • 但是可以通过 this.state.name = 'rose' 方式设置state(不推荐!!!!)
              // 例:
              class Hello extends React.Component {
                constructor() {
                  // es6继承必须用super调用父类的constructor
                  super()
              
                  this.state = {
                    gender: 'male'
                  }
                }
              
                render() {
                  return (
                    <div>性别:{ this.state.gender }</div>
                  )
                }
              }
          
    • JSX语法转化过程
          // 1、JSX
          const element = (
            <h1 className="greeting">
              Hello, world!
            </h1>
          )
          
          // 2、JSX -> createElement
          const element = React.createElement(
            'h1',
            {className: 'greeting'},
            'Hello, world!'
          )
          
          // React elements: 使用对象的形式描述页面结构
          // Note: 这是简化后的对象结构
          const element = {
            type: 'h1',
            props: {
              className: 'greeting',
            },
            children: ['Hello, world']
          }
      
    • style样式:
          // 1. 直接写行内样式:
          <li style={{border:'1px solid red', fontSize:'12px'}}></li>
          
          // 2. 抽离为对象形式
          var styleH3 = {color:'blue'}
          var styleObj = {
            liStyle:{border:'1px solid red', fontSize:'12px'},
            h3Style:{color:'green'}
          }
          
          <li style={styleObj.liStyle}>
            <h3 style={styleObj.h3Style}>评论内容:{props.content}</h3>
          </li>
          
          // 3. 使用样式表定义样式:
          import '../css/comment.css'
          <p className="pUser">评论人:{props.user}</p>
      
    • this
          export default class Test extends React.Component {
              constructor(props) {
                  super(props);
              }
              // this--当前实例
              handleAdd = () => {
                  console.log(this);
              }
              // this-- window 
              handleClick() {
                  console.log(this);
              }
              render() {
                  return (
                      <div className="app">
                          <div onClick={this.handleClick.bind(this)}></div>
                          <div onClick={this.handleAdd }></div>
                      </div>
                      
                  )
              }
          } 
      
  • 组件的生命周期:一个组件从开始到最后消亡所经历的各种状态,就是一个组件的生命周期
    • 组件的定义: 组件生命周期函数的定义:从组件被创建,到组件挂载到页面上运行,再到页面关闭组件被卸载,这三个阶段总是伴随着组件各种各样的事件,那么这些事件,统称为组件的生命周期函数!
    • 通过这个函数,能够让开发人员的代码,参与到组件的生命周期中。也就是说,通过钩子函数,就可以控制组件的行为
  • 生命周期函数总览:
    • 组件的生命周期包含三个阶段:创建阶段(Mounting)、运行和交互阶段(Updating)、卸载阶段(Unmounting)
    • Mounting
          constructor() componentWillMount() render() componentDidMount()
      
    • Updating
          componentWillReceiveProps() shouldComponentUpdate() componentWillUpdate() render() componentDidUpdate()
      
    • Unmounting
          componentWillUnmount()
      
    • 组件生命周期-创建阶段(Mounting)
      • 特点:该阶段的函数只执行一次
      • constructor()
        • 作用:1. 获取props 2. 初始化state
        • 说明:通过 constructor() 的参数props获取
            class Greeting extends React.Component {
              constructor(props) {
                // 获取 props
                super(props)
                // 初始化 state
                this.state = {
                  count: props.initCount
                }
              }
            }
            
            // 初始化 props
            // 语法:通过静态属性 defaultProps 来初始化props
            Greeting.defaultProps = {
              initCount: 0
            };
        
      • componentWillMount()
        • 说明:组件被挂载到页面之前调用,其在render()之前被调用,因此在这方法里同步地设置状态将不会触发重渲染
        • 注意:无法获取页面中的DOM对象
        • 注意:可以调用 setState() 方法来改变状态值
        • 用途:发送ajax请求获取数据
            componentWillMount() {
              console.warn(document.getElementById('btn')) // null
              this.setState({
                count: this.state.count + 1
              })
            }
        
      • render()
        • 作用:渲染组件到页面中,无法获取页面中的DOM对象
        • 注意:不要在render方法中调用 setState() 方法,否则会递归渲染
          • 原因说明:状态改变会重新调用render(),render()又重新改变状态
            render() {
              console.warn(document.getElementById('btn')) // null
            
              return (
                <div>
                  <button id="btn" onClick={this.handleAdd}>打豆豆一次</button>
                  {
                    this.state.count === 4
                    ? null
                    : <CounterChild initCount={this.state.count}></CounterChild>
                  }
                </div>
              )
            }
        
      • componentDidMount()
        • 组件已经挂载到页面中
        • 可以进行DOM操作,比如:获取到组件内部的DOM对象
        • 可以发送请求获取数据
        • 可以通过 setState() 修改状态的值
        • 注意:在这里修改状态会重新渲染
            componentDidMount() {
              // 此时,就可以获取到组件内部的DOM对象
              console.warn('componentDidMount', document.getElementById('btn'))
            }
        
    • 组件生命周期 - 运行阶段(Updating)
      • 特点:该阶段的函数执行多次
      • 说明:每当组件的props或者state改变的时候,都会触发运行阶段的函数
      • componentWillReceiveProps()
        • 说明:组件接受到新的props前触发这个方法
        • 参数:当前组件props值
        • 可以通过 this.props 获取到上一次的值
        • 使用:若你需要响应属性的改变,可以通过对比this.props和nextProps并在该方法中使用this.setState()处理状态改变
        • 注意:修改state不会触发该方法
              componentWillReceiveProps(nextProps) {
               console.warn('componentWillReceiveProps', nextProps)
              }
          
          
      • shouldComponentUpdate()
        • 作用:根据这个方法的返回值决定是否重新渲染组件,返回true重新渲染,否则不渲染
        • 优势:通过某个条件渲染组件,降低组件渲染频率,提升组件性能
        • 说明:如果返回值为false,那么,后续render()方法不会被调用
        • 注意:这个方法必须返回布尔值!!!
        • 场景:根据随机数决定是否渲染组件
            // - 参数:
            //   - 第一个参数:最新属性对象
            //   - 第二个参数:最新状态对象
            shouldComponentUpdate(nextProps, nextState) {
              console.warn('shouldComponentUpdate', nextProps, nextState)
            
              return nextState.count % 2 === 0
            }
        
      • componentWillUpdate()
        • 作用:组件将要更新
        • 参数:最新的属性和状态对象
        • 注意:不能修改状态 否则会循环渲染
            componentWillUpdate(nextProps, nextState) {
              console.warn('componentWillUpdate', nextProps, nextState)
            }
        
      • render() 渲染
        • 作用:重新渲染组件,与Mounting阶段的render是同一个函数
        • 注意:这个函数能够执行多次,只要组件的属性或状态改变了,这个方法就会重新执行
      • componentDidUpdate()
        • 作用:组件已经被更新
        • 参数:旧的属性和状态对象
            componentDidUpdate(prevProps, prevState) {
              console.warn('componentDidUpdate', prevProps, prevState)
            }
        
    • 组件生命周期 - 卸载阶段(Unmounting)
      • 组件销毁阶段:组件卸载期间,函数比较单一,只有一个函数,这个函数也有一个显著的特点:组件一辈子只能执行一次!
      • 使用说明:只要组件不再被渲染到页面中,那么这个方法就会被调用( 渲染到页面中 -> 不再渲染到页面中 )
      • componentWillUnmount()
        • 作用:在卸载组件的时候,执行清理工作,比如
          • 清除定时器
          • 清除componentDidMount创建的DOM对象
    • react 补充:
      • state和setState
        • 注意:使用 setState() 方法修改状态,状态改变后,React会重新渲染组件
        • 注意:不要直接修改state属性的值,这样不会重新渲染组件!!!
        • 使用:1 初始化state 2 setState修改state
            // 修改state(不推荐使用)
            // https://facebook.github.io/react/docs/state-and-lifecycle.html#do-not-modify-state-directly
            this.state.test = '这样方式,不会重新渲染组件';
            constructor(props) {
              super(props)
            
              // 正确姿势!!!
              // -------------- 初始化 state --------------
              this.state = {
                count: props.initCount
              }
            }
            
            componentWillMount() {
              // -------------- 修改 state 的值 --------------
              // 方式一:
              this.setState({
                count: this.state.count + 1
              })
            
              this.setState({
                count: this.state.count + 1
              }, function(){
                // 由于 setState() 是异步操作,所以,如果想立即获取修改后的state
                // 需要在回调函数中获取
                // https://doc.react-china.org/docs/react-component.html#setstate
              });
            
              // 方式二:
              this.setState(function(prevState, props) {
                return {
                  counter: prevState.counter + props.increment
                }
              })
            
              // 或者 - 注意: => 后面需要带有小括号,因为返回的是一个对象
              this.setState((prevState, props) => ({
                counter: prevState.counter + props.increment
              }))
            }
        
      • 组件绑定事件
        • 通过React事件机制 onClick 绑定
        • JS原生方式绑定(通过 ref 获取元素)
          • 注意:ref 是React提供的一个特殊属性
        • 事件名称采用驼峰命名法
      • 事件绑定中的this
        • 通过 bind 绑定
          • 原理:bind能够调用函数,改变函数内部this的指向,并返回一个新函数
          • 说明:bind第一个参数为返回函数中this的指向,后面的参数为传给返回函数的参数
                // 自定义方法:
                handleBtnClick(arg1, arg2) {
                  this.setState({
                    msg: '点击事件修改state的值' + arg1 + arg2
                  })
                }
                
                render() {
                  return (
                    <div>
                      <button onClick={
                        // 无参数
                        // this.handleBtnClick.bind(this)
                
                        // 有参数
                        this.handleBtnClick.bind(this, 'abc', [1, 2])
                      }>事件中this的处理</button>
                      <h1>{this.state.msg}</h1>
                    </div>
                  )
                }
            
          • 在构造函数中使用bind
                constructor() {
                  super()
                
                  this.handleBtnClick = this.handleBtnClick.bind(this)
                }
                
                // render() 方法中:
                <button onClick={ this.handleBtnClick }>事件中this的处理</button>
            
        • 通过 箭头函数 绑定
          • 原理:箭头函数中的this由所处的环境决定,自身不绑定this
                <input type="button" value="在构造函数中绑定this并传参" onClick={
                  () => { this.handleBtnClick('参数1', '参数2') }
                } />
                
                handleBtnClick(arg1, arg2) {
                  this.setState({
                    msg: '在构造函数中绑定this并传参' + arg1 + arg2
                  });
                }
            
      • React 单向数据流
        • React 中采用单项数据流
        • 数据流动方向:自上而下,也就是只能由父组件传递到子组件
        • 数据都是由父组件提供的,子组件想要使用数据,都是从父组件中获取的
        • 如果多个组件都要使用某个数据,最好将这部分共享的状态提升至他们最近的父组件当中进行管理
        • react中的单向数据流动:
          • 数据应该是从上往下流动的,也就是由父组件将数据传递给子组件
          • 数据应该是由父组件提供,子组件要使用数据的时候,直接从子组件中获取
      • 组件通讯
        • 父 -> 子:props
        • 子 -> 父:父组件通过props传递回调函数给子组件,子组件调用函数将数据作为参数传递给父组件
        • 兄弟组件:因为React是单向数据流,因此需要借助父组件进行传递,通过父组件回调函数改变兄弟组件的props
        • React中的状态管理: flux(提出状态管理的思想) -> Redux -> mobx
        • Vue中的状态管理: Vuex

react-router

  • react-router/react-router-dom区别
    • api对比:
      • react-router: 提供了router的核心api。如Router、Route、Switch等,但没有提供有关dom操作进行路由跳转的api
      • React-router-dom:提供了BrowserRouter、Route、Link等api,可以通过dom操作触发事件控制路由
    • 功能对比:
      • react-router:实现了路由的核心功能
      • React-router-dom:基于React-router,加入了一些在浏览器运行下的一些功能,
      • 例如:Link组件会渲染一个a标签,BrowserRouter使用 HTML5 提供的 history API可以保证你的 UI 界面和 URL 保持同步,HashRouter使用 URL 的 hash 部分保证你的 UI 界面和 URL 保持同步
    • 写法对比:
          import {Switch, Route, Router} from 'react-router';
          import {HashHistory, Link} from 'react-router-dom';
          //React-router-dom在React-router的基础上扩展了可操作dom的api
          import {Swtich, Route, Router, HashHistory, Link} from 'react-router-dom';
      
    • 路由跳转对比:
      • React-router:router4.0以上版本用this.props.history.push('/path')实现跳转;router3.0以上版本用this.props.router.push('/path')实现跳转
      • React-router-dom:直接用this.props.history.push('/path')就可以实现跳转
    • 总结:
      • 在使用React的大多数情况下,我们会想要通过操作dom来控制路由,例如点击一个按钮完成跳转,这种时候使用React-router-dom就比较方便。
      • 安装也很简单:npm install react-router-dom --save
      • 从react-router-dom中package.json的依赖就可以看出:react-router-dom是依赖react-router的 ,所以我们使用npm安装react-router-dom的时候,不需要npm安装react-router。
  • 安装react-router-dom npm i -s react-router-dom
  • 路由的基本概念
    • 现在的react-route版本中已经不需要路由配置,现在一切皆组件,并且react-router中提供一下三大组件,即:Router是所有路由组件共用的底层接口组件,他是路由规则制定最外层的容器;Route路由规则匹配,并显示当前的规则对应组件;Link路由跳转组件,每个组件下又会有几种不同的子类组件实现,比如:Router组件针对 不同功能和平的应用:
        <BrowserRouter> 浏览器的路由组件
        <HashRouter> URL格式为Hash路由组件
        <MemoryRouter> 内存路由组件
        <NativeRouter> Native的路由组件
        <StaticRouter> 地址不改变的静态路由组件
        
        example:如果说我们的应用程序是一座小城的话
        ,那么Route就是一座座带有门牌号的建筑物,
        而Link就代表了到某个建筑物的路线。
        有了路线和目的地,那么就缺一位老司机了,
        没错Router就是这个老司机。
    
        import React, { Component } from 'react';
        import { HashRouter as Router, Link, Route } from 'react-router-dom';
        import './App.css';
        
        const Home = () => (
          <div>
            <h2>Home</h2>
          </div>
        )
        
        const About = () => (
          <div>
            <h2>About</h2>
          </div>
        )
        
        const Product = () => (
          <div>
            <h2>Product</h2>
          </div>
        )
        
        class App extends Component {
          render() {
            return (
              <Router>
                <div className="App">
                  <Link to="/">Home</Link>
                  <Link to="/About">About</Link>
                  <Link to="/Product">Product</Link>
                  <hr/>
                  <Route path="/" exact component={Home}></Route>
                  <Route path="/about" component={About}></Route>
                  <Route path="/product" component={Product}></Route>
                </div>
              </Router>
            );
          }
        }
        
        export default App;
    
  • Router组件
    • BrowserRouter组件:
      • BrowserRouter主要使用在浏览器中,也就是WEB应用中。它利用HTML5 的history API来同步URL和UI的变化。当我们点击了程序中的一个链接之后,BrowserRouter就会找出与这个URL匹配的Route,并将他们对应的组件渲染出来。BrowserRouter是用来管理我们的组件的,那么它当然要被放在最顶级的位置,而我们的应用程序的组件就作为它的一个子组件而存在。
      import * as React from 'react';
      import * as ReactDOM from 'react-dom';
      import { BrowserRouter } from 'react-router-dom';
      
      ReactDOM.render(
          <BrowserRouter>
              <App/>
          </BrowserRouter>,
          document.body);
      
    • BrowserRouter组件提供了四个属性:
      • basename: 字符串类型,路由器的默认根路径
      • forceRefresh: 布尔类型,在导航的过程中整个页面是否刷新
      • getUserConfirmation: 函数类型,当导航需要确认时执行的函数。默认是:window.confirm
      • keyLength: 数字类型location.key 的长度。默认是 6
    • basename 属性
      • 当前位置的基准 URL。如果你的页面部署在服务器的二级(子)目录,你需要将 basename 设置到此子目录。正确的 URL 格式是前面有一个前导斜杠,但不能有尾部斜杠。
      • 例如:有时候我们的应用只是整个系统中的一个模块,应用中的URL总是以 http://localhost/admin/ 开头。这种情况下我们总不能每次定义Link和Route的时候都带上admin吧?react-router已经考虑到了这种情况,所以为我们提供了一个basename属性。为BrowserRouter设置了basename之后,Link中就可以省略掉admin了,而最后渲染出来的URL又会自动带上admin。
      <BrowserRouter basename="/admin"/>
          ...
          <Link to="/home"/> // 被渲染为 <a href="/admin/home">
          ...
      </BrowserRouter>
      
    • getUserConfirmation: func :当导航需要确认时执行的函数。默认使用 window.confirm。
      // 使用默认的确认函数
      const getConfirmation = (message, callback) => {
        const allowTransition = window.confirm(message)
        callback(allowTransition)
      }
      
      <BrowserRouter getUserConfirmation={getConfirmation}/>
      
    • forceRefresh:
      • bool当设置为 true时,在导航的过程中整个页面将会刷新。 只有当浏览器不支持 HTML5 的 history API 时,才设置为 true
      const supportsHistory = 'pushState' in window.history
      <BrowserRouter forceRefresh={!supportsHistory}/>
      
    • keyLength: number
      • location.key 的长度。默认是 6。
          <BrowserRouter keyLength={12}/>
      
    • HashRouter :HashRouter 使用 URL 的 hash (例如:window.location.hash) 来保持 UI 和 URL 的同步。
      • 注意:使用 hash 的方式记录导航历史不支持 location.key和location.state。在以前的版本中,我们为这种行为提供了shim,但是仍有一些问题我们无法解。任何依赖此行为的代码或插件都将无法正常使用。由于该技术仅用于支持传统的浏览器,因此在用于浏览器时可以使用 代替。
      • 跟BrowserRouter类似,它也有:basename、getUserConfirmation、children属性,而且是一样的。
      • hashType: string
        window.location.hash 使用的 hash 类型。有如下几种:
        "slash" - 后面跟一个斜杠,例如 #/ 和 #/sunshine/lollipops
        "noslash" - 后面没有斜杠,例如 # 和 #sunshine/lollipops
        "hashbang" - Google 风格的 "ajax crawlable",例如 #!/ 和 #!/sunshine/lollipops
        默认为 "slash"
    • MemoryRouter :主要用在ReactNative这种非浏览器的环境中,因此直接将URL的history保存在了内存中。 StaticRouter 主要用于服务端渲染。
    • Link组件:
      • Link就像是一个个的路牌,为我们指明组件的位置。Link使用声明式的方式为应用程序提供导航功能,定义的Link最终会被渲染成一个a标签。Link使用to这个属性来指明目标组件的路径,可以直接使用一个字符串,也可以传入一个对象。
            import { Link } from 'react-router-dom'
            // 字符串参数
            <Link to="/query">查询</Link>
            
            // 对象参数
            <Link to={{
              pathname: '/query',
              search: '?key=name',
              hash: '#hash',
              state: { fromDashboard: true }
            }}>查询</Link>
        
      • 属性:to --- 需要跳转到的路径(pathname)或地址(location。
      • 属性:replace: bool :
            // 当设置为 true 时,点击链接后将使用新地址替换掉访问历史记录里面的原地址。
            // 当设置为 false 时,点击链接后将在原有访问历史记录的基础上添加一个新的纪录。
            //默认为 false。
            <Link to="/courses" replace />
        
    • NavLink组件 :
      • NavLink是一个特殊版本的Link,可以使用activeClassName来设置Link被选中时被附加的class,使用activeStyle来配置被选中时应用的样式。此外,还有一个exact属性,此属性要求location完全匹配才会附加class和style。这里说的匹配是指地址栏中的URl和这个Link的to指定的location相匹配。
            // 选中后被添加class selected
            <NavLink to={'/'} exact activeClassName='selected'>Home</NavLink>
            // 选中后被附加样式 color:red
            <NavLink to={'/gallery'} activeStyle={{color:red}}>Gallery</NavLink>
            // *** activeClassName默认值为 active
            
        
      • 属性
            to --  可以是字符串或者对象,同Link组件
            exact -- 布尔类型,完全匹配时才会被附件class和style 
            activeStyle Object类型
            activeClassName 字符串类型
            strict: bool类型,当值为 true 时,在确定位置是否与当前 URL 匹配时,将考虑位置 pathname 后的斜线。
        
    • Route组件
      • Route应该是react-route中最重要的组件了,它的作用是当location与Route的path匹配时渲染
      • Route中的Component。如果有多个Route匹配,那么这些Route的Component都会被渲染。
      • 与Link类似,Route也有一个exact属性,作用也是要求location与Route的path绝对匹配。
            // 当location形如 http://location/时,Home就会被渲染。
            // 因为 "/" 会匹配所有的URL,所以这里设置一个exact来强制绝对匹配。
            <Route exact path="/" component={Home}/>
            <Route path="/about" component={About}/>
        
      • Route的三种渲染方式
        • component: 这是最常用也最容易理解的方式,给什么就渲染什么。
        • render: render的类型是function,Route会渲染这个function的返回值。因此它的作用就是附加一些额外的逻辑。
        • children: 这是最特殊的渲染方式。
          • 它同render类似,是一个function。不同的地方在于它会被传入一个match参数来告诉你这个Route的path和location匹配上没有。
          • 第二个特殊的地方在于,即使path没有匹配上,我们也可以将它渲染出来。秘诀就在于前面一点提到的match参数。我们可以根据这个参数来决定在匹配的时候渲染什么,不匹配的时候又渲染什么。
          // 在匹配时,容器的calss是light,<Home />会被渲染
          // 在不匹配时,容器的calss是dark,<About />会被渲染
          <Route path='/home' children={({ match }) => (
              <div className={match ? 'light' : 'dark'}>
                {match ? <Home/>:<About>}
              </div>
            )}/>
          
        • 所有路由中指定的组件将被传入以下三个props:matchlocation,history.
          • location:是指你当前的位置,下一步打算去的位置,或是你之前所在的位置,形式大概就像这样
              {
                key: 'ac3df4', // 在使用 hashHistory 时,没有 key
                pathname: '/somewhere'
                search: '?some=search-string',
                hash: '#howdy',
                state: {
                  [userDefined]: true
                }
              }
          
          • 你使用以下几种方式来获取 location 对象:
            • 在 Route component 中,以this.props.location 的方式获取,
            • 在 Route render 中,以 ({ location }) => () 的方式获取,
            • 在 Route children 中,以 ({ location }) => () 的方式获取,
            • 在 withRouter 中,以this.props.location 的方式获取
          • 你也可以在 history.location 中获取 location 对象,但是别那么写,因为 history 是可变的。更多信息请参见 history 文档。
              componentWillReceiveProps(nextProps) {
                  if (nextProps.location !== this.props.location) {
                      // 已经跳转了!
                    }
              }
          
          • 通常情况下,你只需要给一个字符串当做 location ,但是,当你需要添加一些 location 的状态时,你可以对象的形式使用 location 。并且当你需要多个 UI ,而这些 UI 取决于历史时,例如弹出框(modal),使用location 对象会有很大帮助。
              // 通常你只需要这样使用 location
              <Link to="/somewhere"/>
              
              // 但是你同样可以这么用
              const location = {
                pathname: '/somewhere'
                state: { fromDashboard: true }
              }
              
              <Link to={location}/>
              <Redirect to={location}/>
              history.push(location)
              history.replace(location)
          
        • history
          • 本文档中的「history」以及「history对象」请参照 history 包中的内容。 History 是 React Router 的两大重要依赖之一(除去 React 本身),在不同的 Javascript 环境中,history 以多种形式实现了对于 session 历史的管理。
          • history 对象通常会具有以下属性和方法:
            • length -( number 类型)指的是 history 堆栈的数量。
            • action -( string 类型)指的是当前的动作(action),例如 PUSH,REPLACE 以及 POP 。
            • location -( object类型)是指当前的位置(location),location 会具有如下属性:
              • pathname -( string 类型)URL路径。
              • hash -( string 类型)URL的 hash 分段。
              • state -( string 类型)是指 location 中的状态,例如在 push(path, state) 时,state会描述什么时候 location 被放置到堆栈中等信息。这个 state 只会出现在 browser history 和 memory history 的环境里。
            • push(path, [state]) -( function 类型)在 hisotry 堆栈顶加入一个新的条目。
            • go(n) -( function 类型)将 history 对战中的指针向前移动 n 。
            • goBack() -( function 类型)等同于 go(-1) 。
            • goForward() -( function 类型)等同于 go(1) 。
            • block(prompt) -( function 类型)阻止跳转,(请参照 history 文档)
        • match
          • match 对象包含了 如何与URL匹配的信息。match 对象包含以下属性:
          • params -( object 类型)即路径参数,通过解析URL中动态的部分获得的键值对。
          • isExact - 当为 true 时,整个URL都需要匹配。
          • path -( string 类型)用来做匹配的路径格式。在需要嵌套 的时候用到。
          • url -( string 类型)URL匹配的部分,在需要嵌套 的时候会用到。
          • 你可以在以下地方获取 match 对象:
            • 在 Route component 中,以 this.props.match 方式。
            • 在 Route render中,以 ({ match }) => () 方式。
            • 在 Route children中,以 ({ match }) => () 方式
    • Redirect组件
      • 当这个组件被渲染是,location会被重写为Redirect的to指定的新location。它的一个用途是登录重定向,比如在用户点了登录并验证通过之后,将页面跳转到个人主页。
          <Redirect to="/new"/>
      
    • Switch组件
      • 渲染匹配地址(location)的第一个 或者
      • 的独特之处是独它仅仅渲染一个路由。相反地,每一个包含匹配地址(location)的都会被渲染。思考下面的代码
          <Route path="/about" component={About}/>
          <Route path="/:user" component={User}/>
          <Route component={NoMatch}/>
      
      • 如果现在的URL是 /about ,那么 , , 还有 都会被渲染,因为它们都与路径(path)匹配。这种设计,允许我们以多种方式将多个 组合到我们的应用程序中,例如侧栏(sidebars),面包屑(breadcrumbs),bootstrap tabs等等。 然而,偶尔我们只想选择一个 来渲染。如果我们现在处于 /about,我们也不希望匹配 /:user (或者显示我们的 "404" 页面 )。以下是使用 Switch 的方法来实现:
      • 现在,如果我们处于 /about, 将开始寻找匹配的 。 将被匹配, 将停止寻找匹配并渲染。 同样,如果我们处于 /michael , 将被渲染。
          import { Switch, Route } from 'react-router'
      
          <Switch>
            <Route exact path="/" component={Home}/>
            <Route path="/about" component={About}/>
            <Route path="/:user" component={User}/>
            <Route component={NoMatch}/>
          </Switch>
      
    • BrowserRouter和HashRouter的区别
      • 从原理上 HashRouter在路径中包含了#,相当于HTML的锚点定位。(# 符号的英文叫hash,所以叫HashRouter,和散列没关系哦)
      • 从用法上 BrowserRouter进行组件跳转时可以传递任意参数实现组件间的通信,而HashRouter不能(除非手动拼接URL字符串),因此一般配合Redux使用,实现组件间的数据通信。
      • 生产实践
        • HashRouter: HashRouter相当于锚点定位,因此不论#后面的路径怎么变化,请求的都相当于是#之前的那个页面。可以很容易的进行前后端不分离的部署(也就是把前端打包后的文件放到服务器端的public或static里),因为请求的链接都是ip地址:端口/#/xxxx,因此请求的资源路径永远为/,相当于index.html,而其他的后端API接口都可以正常请求,不会和/冲突,由于前后端不分离也不会产生跨域问题。
        • BrowserRouter: 因为BrowserRouter模式下请求的链接都是ip地址:端口/xxxx/xxxx,因此相当于每个URL都会访问一个不同的后端地址,如果后端没有覆盖到路由就会产生404错误,可以通过加入中间件解决,放在服务器端路由匹配的最后,如果前面的API接口都不匹配,则返回index.html页面。但这样也有一些小问题,因为要求前端路由和后端路由的URL不能重复。比如商品列表组件叫/product/list,而请求商品列表的API也是/product/list,那么就会访问不到页面,而是被API接口匹配到。
        • 解决: 进行前后端分离的部署,比如前端地址ip1:端口1,后端接口地址ip2:端口2,使用Nginx反向代理服务器进行请求分发。前端向后端发起请求的URL为nginx所在的服务器+/api/xxx,通过NGINX的配置文件判断,如果URL以api开头则转发至后端接口,否则转发至前端的地址,访问项目只需访问Nginx服务器即可
    • 路由封装 -- 看代码----

redux

  • 设计思想:
    • web应用是一个状态机,视图与状态是一一对应的
    • 所有的状态,保存在一个对象里面。
  • 基本概念和API
    • store
      • store就是保存数据的地方,你可以把它看成是一个容器,整个应用只用一个store,redux提供createStore这个函数,来生成Store
          import { createStore } from 'redux'
          const store = createStore()//reducer ,init ,enhance
      
    • state
      • store对象包含所有的数据,如果想得到某个时点的数据,就要对store生成快照,这种时点的数据集合,就是叫做state,当前时刻的state,可以通过store.getState()拿到。
          import { createStore } from 'redux';
          const store = createStore();
          
          const state = store.getState();
      
    • action
      • state的变化,会导致view的变化,但是,用户接触不到state,只能接触到view,所以state必须是view导致的,action就是view 发出的通知,表示state应该要发生变化了。action是一个对像,其中type属性是必须的,表示action的名称,其他属性自由设置。
          const action = {
              type: 'ADD_TODO',
              text: 'learn redux'
          }
      
      • 上面的代码中,action的名称就是ADD_TODO',携带的信息字符串是learn redux,action描述当前发生的事情,改拜年state唯一的办法,就是使用action,它会运送到数据到store
    • action cerator
      • 定义一个函数
          const ADD_TODO = '添加 TODO';
      
          function addTodo(text) {
            return {
              type: ADD_TODO,
              text
            }
          }
          
          const action = addTodo('Learn Redux');
      
    • store.dispatch(): 是view发出action唯一方法
          
          import { createStore } from 'redux';
          const store = createStore(fn);
          
          store.dispatch({
            type: 'ADD_TODO',
            text: 'Learn Redux'
          });
      
    • reducer
      • Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
      • Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
          const reducer = function (state, action) {
            // ...
            return new_state;
          };
          
          
          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 函数不用像上面这样手动调用,store.dispatch方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。
      • 上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。
    • 纯函数
      • Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。纯函数是函数式编程的概念,必须遵守以下一些约束。
        • 不得改写参数
        • 不能调用系统 I/O 的API
        • 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
          // State 是一个对象
          function reducer(state, action) {
            return Object.assign({}, state, { thingToChange });
            // 或者
            return { ...state, ...newState };
          }
          
          // State 是一个数组
          function reducer(state, action) {
            return [...state, newItem];
          }
      
    • store.subscribe()
      • Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
          import { createStore } from 'redux';
          const store = createStore(reducer);
          
          store.subscribe(listener);
          
      
    • store的实现
          const createStore = (reducer) => {
            let state;
            let listeners = [];
          
            const getState = () => state;
          
            const dispatch = (action) => {
              state = reducer(state, action);
              listeners.forEach(listener => listener());
            };
          
            const subscribe = (listener) => {
              listeners.push(listener);
              return () => {
                listeners = listeners.filter(l => l !== listener);
              }
            };
          
            dispatch({});
          
            return { getState, dispatch, subscribe };
          };
      
    • reducer的拆分
      • Redux 提供了一个combineReducers方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。
          import { combineReducers } from 'redux';
      
          const chatReducer = combineReducers({
            chatLog,
            statusMessage,
            userName
          })
          
          export default todoApp;
      
  • react-redux的用法
    • UI组件:
      • 只负责UI的呈现,不带有任何业务逻辑,没有状态(即不使用this.state这个变量),所有的数据都由参数(this.props)提供,不使用任何的redux的API--纯组件
          const Title =
            value => <h1>{value}</h1>;
      
    • 容器组件:
      • 负责管理数据和业务逻辑,不负责UI的呈现,带有内部状态,使用redux的API。
      • React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它
    • connect()
      • React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。
          import { connect } from 'react-redux'
          const VisibleTodoList = connect()(TodoList);
      
      • 上面代码中,TodoList是 UI 组件,VisibleTodoList就是由 React-Redux 通过connect方法自动生成的容器组件。
      • 输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数 输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去。
          import { connect } from 'react-redux'
      
          const VisibleTodoList = connect(
            mapStateToProps,
            mapDispatchToProps
          )(TodoList)
      
      • 上面代码中,connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
    • mapStateToProps()
      • mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。
      • 作为函数,mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。请看下面的例子。
          const mapStateToProps = (state) => {
            return {
              todos: getVisibleTodos(state.todos, state.visibilityFilter)
            }
          }
      
      • 上面代码中,mapStateToProps是一个函数,它接受state作为参数,返回一个对象。这个对象有一个todos属性,代表 UI 组件的同名参数,后面的getVisibleTodos也是一个函数,可以从state算出 todos 的值。
      • 下面就是getVisibleTodos的一个例子,用来算出todos。
          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对象。
          const mapStateToProps = (state, ownProps) => {
            return {
              active: ownProps.filter === state.visibilityFilter
            }
          }
      
      • 使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染。
      • connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。
    • mapDispatchToProps()
      • mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。
      • 如果mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数
          const mapDispatchToProps = (
            dispatch,
            ownProps
          ) => {
            return {
              onClick: () => {
                dispatch({
                  type: 'SET_VISIBILITY_FILTER',
                  filter: ownProps.filter
                });
              }
            };
          }
      
      • 从上面代码可以看到,mapDispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。
    • 组件
      • connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。
      • 一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。
      • React-Redux 提供Provider组件,可以让容器组件拿到state。
          import { Provider } from 'react-redux'
          import { createStore } from 'redux'
          import todoApp from './reducers'
          import App from './components/App'
          
          let store = createStore(todoApp);
          
          render(
            <Provider store={store}>
              <App />
            </Provider>,
            document.getElementById('root')
          )
          
          <Provider store={store}>
              <Router>
                <Route path="/" component={App} />
              </Router>
          </Provider>
      
      • 上面代码中,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。