React全家桶(技术栈)

尚硅谷前端研究院

第1章:React入门

1.1. React的基本认识

1.1.1. 介绍描述

  • 用于构建用户界面的 JavaScript 库(只关注于View)
  • 由Facebook开源

    1.1.2. React的特点

  • Declarative(声明式编码)

  • Component-Based(组件化编码)
  • Learn Once, Write Anywhere(支持客户端与服务器渲染)
  • 高效
  • 单向数据流

    1.1.3. React高效的原因

  • 虚拟(virtual)DOM, 不总是直接操作DOM

  • DOM Diff算法, 最小化页面重绘

第一次挂载组件并执行第一次渲染时,也是先生成虚拟DOM,然后解析为真实DOM,再次渲染时,会根据新生成的虚拟DOM来比对原来真实的DOM,然后根据diff算法实现局部更新页面。但是react的比对并不包含标签位置的比对,所以在一些列表的渲染中,要给每一项加上唯一的标识,即key属性。

所以React的高效依赖于所谓的 Virtual-DOM,尽量不碰真实的DOM。

1.2. React的基本使用

注意: 此时只是测试语法使用, 并不是真实项目开发使用

1.2.1. 效果

React全家桶(技术栈) - 图1

1.2.2. 相关js库

1) react.js: React的核心库
2) react-dom.js: 提供操作DOM的react扩展库
3) babel.min.js: 解析JSX语法代码转为纯JS语法代码的库

1.2.3. 在页面中导入js

| <script type=”text/javascript” src=”../js/react.development.js”></script> <script type=”text/javascript” src=”../js/react-dom.development.js”></script> <script type=”text/javascript” src=”../js/babel.min.js”></script> | | —- |

1.2.4. 编码

  1. <script type="text/babel"> //必须声明babel
  2. // 创建虚拟DOM元素
  3. const vDom = <h1>Hello React</h1> // 千万不要加引号
  4. // 渲染虚拟DOM到页面真实DOM容器中
  5. ReactDOM.render(vDom, document.getElementById('test'))
  6. </script>
  7. /* 此时vDom也叫react元素 */

React全家桶(技术栈) - 图2(标签内别加双引号)

1.2.5. React开发者工具调试

(chrome浏览器的一个扩展)

1.3. React JSX

1.3.1. 效果

React全家桶(技术栈) - 图3

1.3.2. 虚拟DOM

  • React提供了一些API来创建一种 特别 的一般js对象
    • var element = React.createElement(‘h1’, {id:’myTitle’},’hello’)

上面的字符串都可以用变量来表示。

  • 上面创建的就是一个简单的虚拟DOM对象

    • 虚拟DOM对象最终都会被React转换为真实的DOM
    • 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界面




    真实的DOM是非常重的文档对象模型。
    React全家桶(技术栈) - 图4蓝色框中有很多信息,滚动条能滑很久。
    虚拟DOM则很轻。
    React全家桶(技术栈) - 图5


    更新真实的DOM页面就发生新变化,即重绘。
    更新虚拟DOM时页面不会更新,除非执行渲染。

    1.3.3. JSX

  • 全称: JavaScript XML

React全家桶(技术栈) - 图6

  • react定义的一种类似于XML的JS扩展语法: XML+JS
    • XML和HTML类似,与HTML不同,XML的标签名可以自定义。
    • 所以在JSX中,除了可以写HTML的标签名,还可以自定义标签名。(这种标签叫做组件标签)
  • 作用: 用来创建react虚拟DOM(元素)对象
    • var ele = <h1>Hello JSX!</h1>
    • 注意1: 它不是字符串, 也不是HTML/XML标签
    • 注意2: 它最终产生的就是一个JS对象
  • 标签名任意:HTML标签或其它标签; 标签属性任意:HTML标签属性或其它
  • 基本语法规则

    • 遇到<开头的代码, 以标签的语法解析。

    html同名标签转换为html同名元素, 其它标签需要特别解析

    • 遇到以{开头的代码,以JS语法解析。

    标签中的js代码必须用{ }包含

  • babel.js的作用

    • 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
    • 只要用了JSX,都要加上type=”text/babel”, 声明需要babel来处理

1.3.4. 渲染虚拟DOM(元素)

  • 语法: ReactDOM.render(virtualDOM, containerDOM)
  • 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示(即不渲染则不会显示)
  • 参数说明

    • 参数一: 纯js或jsx创建的虚拟dom对象
    • 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)

      1.3.5. 建虚拟DOM的2种方式

  • 纯JS(一般不用)

React.createElement('h1', {id:'myTitle'}, title)

  • JSX:

<h1 id='myTitle'>{title}</h1>
React全家桶(技术栈) - 图7

*
现在的代码是,浏览器打开的时候动态编译JSX代码,那么这出来的速度是比较慢的,毕竟现在我们只是处于初学的测试语法阶段。真正开发或生产的时候其实是要先编译好。

1.3.6. JSX练习

需求: 动态展示列表数据
React全家桶(技术栈) - 图8

问题:如何将一个数据的数组转换为一个标签的数组?
使用数组的map() (这个非常常用)
React全家桶(技术栈) - 图9
React全家桶(技术栈) - 图10
(注意这里创建vDOM的时候等号后面加了括号)
React全家桶(技术栈) - 图11(不加key的警告)
React全家桶(技术栈) - 图12
React全家桶(技术栈) - 图13(生成一个新的数组)

1.4. 模块与组件和模块化与组件化的理解

1.4.1. 模块

  • 理解: 向外提供特定功能的js程序, 一般就是一个js文件
  • 为什么: js代码更多更复杂
  • 作用: 复用js, 简化js的编写, 提高js运行效率

    1.4.2. 组件

  • 理解: 用来实现特定(局部)功能效果的代码集合(html/css/js)

  • 为什么: 一个界面的功能更复杂
  • 作用: 复用编码, 简化项目编码, 提高运行效率

    1.4.3. 模块化

    形容项目和项目编码方式的。
    当应用的js都以模块来编写的, 这个应用就是一个模块化的应用

    1.4.4. 组件化

    当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
    React全家桶(技术栈) - 图14

    第2章:React面向组件编程

    层级底部是 面向对象,再往上是面向模块, 最后是面向组件。

    2.1. 基本理解和使用

    2.1.1. 效果

    React全家桶(技术栈) - 图15(这里是在react dev tools里面看)
    这里的是组件标签。
    首字母大写,是为了与html标签区分开来。

2.1.2. 简单组件与复杂组件:

  1. 定义组件有两种方式 ```javascript /方式1: 工厂函数组件(简单组件)/ function MyComponent () { return

    工厂函数组件(简单组件:没有状态State的组件)

    }

/方式2: ES6类组件(复杂组件)/ class MyComponent2 extends React.Component {/继承了React的Component组件/ render () { /console.log(this)/ // 这里的this指MyComponent2(组件对象) return

ES6类组件(复杂组件)

} }

  1. 组件的实例对象简称为组件对象。
  2. 简单组件又叫作无状态组件,简单组件没有state。因为状态会带来管理的复杂性,我们尽量多地写简单组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。
  3. > 简单组件/工厂函数组件/函数组件/无状态组件
  4. > 复杂组件/ES6类组件/class组件/有状态组件
  5. <br />
  6. 2. 渲染组件标签
  7. ```javascript
  8. ReactDOM.render(<MyComponent />, document.getElementById('example1'))
  9. ReactDOM.render(<MyComponent2 />, document.getElementById('example2'))

注意两边有单书名号。

2.1.3. 注意

  • 组件名必须首字母大写
  • 虚拟DOM元素只能有一个根元素
  • 虚拟DOM元素必须有结束标签

    2.1.4. render()渲染组件标签的基本流程

  • React内部会创建组件实例对象

  • 得到包含的虚拟DOM并解析为真实DOM
  • 插入到指定的页面元素内部

    2.2. 组件三大属性1: state

    2.2.1. 效果

    React全家桶(技术栈) - 图16点击一下变成React全家桶(技术栈) - 图17

    2.2.2. 理解

  • state是组件对象最重要的属性, 值是对象(即可以包含多个数据)

  • 组件被称为”状态机”, 通过更新组件的state来更新对应的页面显示(重新渲染组件)
  • setState()是异步的;
  • setState()执行一次,渲染一次。

    2.2.3. 编码操作

  • 初始化状态

    1. constructor (props) {
    2. super(props)
    3. this.state = {
    4. stateProp1 : value1,
    5. stateProp2 : value2
    6. }
    7. }
  • 读取某个状态值

    this.state.statePropertyName

  • 更新状态——>组件界面更新

    1. this.setState({
    2. stateProp1 : value1,
    3. stateProp2 : value2
    4. })

为了使handleClick()的this指向组件对象而不是指向undefined,我们可以利用bind()方法。
bind()方法:
bind()方法主要就是将函数绑定到某个对象,bind()会返回一个函数,该函数体内的this的值会被绑定到bind()中第一个参数的值,例如:f.bind(obj),实际上可以理解为obj.f(),这时f函数体内的this自然指向的是obj;

2.3. 组件三大属性2: props

2.3.1. 效果

需求: 自定义用来显示一个人员信息的组件
1). 姓名必须指定
2). 如果性别没有指定, 默认为男
3). 如果年龄没有指定, 默认为18
React全家桶(技术栈) - 图18

2.3.2. 理解

  • 每个组件对象都会有props(properties的简写)属性
  • 组件标签的所有属性都保存在props中
  • 正常情况下,每个组件对象的props属性是外部传入的。(传入方式:外部使用该组件标签,在组件标签中设置属性)(这个“外部”其实就是其父组件)
  • 组件内部也可以通过一些方式来设置props属性的默认值。
  • 每个组件对象的props数据是只读的,但是你可以通过父组件主动重新渲染的方式来传入新的 props。
  • 外部向某组件传递数据就是以props作为载体。
  • 简单组件/函数组件 中 要通过接收props参数来取得props的各种键值对。

2.3.4. 编码操作

  1. 内部读取某个属性值

this.props.propertyName

  1. 对props中的属性值进行类型限制和必要性限制

Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number.isRequired
}
isRequired:指定某属性必须输入值。
React全家桶(技术栈) - 图19
更改为↓(注意大写和小写):
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired
}

  1. 扩展属性:将对象的所有属性通过props传递

更简单的方法。

  1. 组件类的构造函数

constructor (props) {
super(props)
console.log(props) // __查看所有属性
}

2.4. 组件三大属性3: refs与事件处理

2.4.1. 效果

需求: 自定义组件, 功能说明如下:
2. 点击按钮, 提示第一个输入框中的值
3. 当第2个输入框失去焦点时, 提示这个输入框中的值

2.4.2. 组件的3大属性之二: refs属性

ref:references
引用(Refs)提供了一个获得DOM节点或者创建在render方法中的React元素的方法;

  • 组件内的标签都可以定义ref属性来标识自己
    • this.msgInput = input}/>

this.msgInput是指往class组件对象存入了一个属性叫msgInput

  • 回调函数在组件初始化渲染完或卸载时自动调用

    • 在组件中可以通过this.msgInput来得到对应的真实DOM元素
    • 作用: 通过ref获取组件内容特定标签对象, 进行读取其相关数据


    React全家桶(技术栈) - 图20
    React全家桶(技术栈) - 图21(官方其实不建议这样写)

    (注意认真看每一行代码和注释!!)
    React全家桶(技术栈) - 图22

  • 官方建议的写法中,ref=后面跟的是JavaScript代码,里面是一个回调函数,其中里面的参数、字面量必须相同,是可以自定义的。
  • 我感觉给ref设置一个回调函数的意义是,传入一个自定义参数,然后返回 this.自定义属性名 = 自定义参数,即向该组件对象中返回了一个东西,这个东西是对象也好,属性也罢。反正可以在组件对象中再次通过这个 this.属性名 访问对应的标签(这个属性名的值即为自定义的参数),即该自定义参数已经标识了对应的标签。

所以也可以这样
React全家桶(技术栈) - 图23
失去焦点那里,也可以用ref来弄。

2.4.3. 事件处理

  • 通过onXxx属性指定组件的事件处理函数(注意大小写)
    • React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
    • React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
  • 通过event.target得到发生事件的DOM元素对象


handleFocus(event) {
event.target //返回input__对象
}

2.4.4. 强烈注意

  • 组件内置的方法中的this为组件对象(即组件本身存在的方法)
  • 在组件类中自定义的方法中this为null
    • 强制绑定this: 通过函数对象的bind()
    • 箭头函数(ES6模块化编码时才能使用)——因为箭头函数的this指向不同于一般函数。

      2.5. 组件的组合

      2.5.1. 效果

      功能: 组件化实现此功能
      1. 显示所有todo列表
      2. 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本
      React全家桶(技术栈) - 图24React全家桶(技术栈) - 图25


      问题1:
      数据保存在哪个组件内?
      看数据是某个组件需要(给该组件),还是某些组件需要(给它们共同的父组件)。

      问题2:
      需要在子组件中改变父组件的state。
      在子组件中,不能在子组件内部用this.setState来改变父组件的状态(因子组件的this是指向子组件)。
      状态在哪个组件,更新状态的行为就应该定义在哪个组件。
      (更新状态的行为就是一些事件的响应函数)
      解决方案:在父组件定义相应的响应函数(在父组件render()内子组件的组件标签定义,然后子组件能通过props访问该函数,访问该函数通过传参来传递需要更新state的数据),传递给子组件,子组件去调用。
      具体看练习的js文件,不然会不太懂。

      2.5.2. 功能界面的组件化编码流程(无比重要)

      1) **拆分组件**:
      拆分界面,抽取组件
      React全家桶(技术栈) - 图26(一个大组件包含着两个小组件)
      2) **实现静态组件**:
      使用组件实现静态页面效果
      3) **实现动态组件**
      a. 动态显示初始化数据
      b. 交互功能(从绑定事件监听开始)

      2.6. 收集表单数据

      2.6.1. 效果

      需求: 自定义包含表单的组件
      1. 输入用户名密码后, 点击登陆提示输入信息
      3. 不提交表单
      React全家桶(技术栈) - 图27

      2.6.2. 理解

      1) 问题: 在react应用中, 如何收集表单输入数据
      2) 包含表单的组件分类
      a. 受控组件: 表单项输入数据能自动收集成状态(即能实时更新)
      即我们已完成效果的文件里的密码框算是受控组件的本质
      b. 非受控组件: 需要时才手动读取表单输入框中的数据
      即我们已完成效果的文件里的用户框算是受控组件的本质
      因为我们的效果图用了受控组件的内容和非受控组件的内容,所以该组件LoginForm其实是一个混合组件。
      React全家桶(技术栈) - 图28

      这两种组件其实没有说一定要用哪种。(其实受控组件更麻烦一点,但官方文档推荐使用受控组件
      React全家桶(技术栈) - 图29
      因为:受控组件更符合react的思想,而非受控组件操作了原生DOM。
      React全家桶(技术栈) - 图30
      React全家桶(技术栈) - 图31

      其实之间官方文档在refs那里时候也提到过相关东西。
      React全家桶(技术栈) - 图32

      2.7. 组件生命周期

      生命周期:从产生到死亡。
      组件生命周期实际上是组件对象生命周期。

      2.7.1. 效果

      需求: 自定义组件
      1. 让指定的文本做显示/隐藏的渐变动画
      2. 切换持续时间为2S
      3. 点击按钮从界面中移除组件界面

      React全家桶(技术栈) - 图33React全家桶(技术栈) - 图34
      React全家桶(技术栈) - 图35

      2.7.2. 理解

  1. 组件对象从创建到死亡它会经历特定的生命周期阶段
  2. React组件对象包含一系列的钩子函数(生命周期回调函数), 在生命周期特定时刻**回调**
  3. 我们在定义组件时, 可以重写特定的生命周期回调函数, 做特定的工作

    2.7.3. 生命周期流程图

    下图这些如componentWillMount()称为生命周期回调函数,有时也叫生命周期的钩子。

    说白了,这些“钩子”,你不写也会自己调用,并会在生命周期内的特定时刻回调。
    只是说,你不重写这些钩子,就没有机会插入你想做的事情。
    React全家桶(技术栈) - 图36

2.7.4. 生命周期详述

1) 组件的三个生命周期状态:
Mount:插入真实 DOM
Update:被重新渲染
Unmount:被移出真实 DOM
2) React 为每个状态都提供了钩子(hook)函数
componentWillMount()
componentDidMount()
componentWillUpdate()
componentDidUpdate()
componentWillUnmount()
3) 生命周期流程:
a. 第一次初始化渲染显示: ReactDOM.render()
constructor(): 创建对象初始化state
componentWillMount() : 将要插入回调
render() : 用于插入虚拟DOM回调
componentDidMount() : 已经插入回调
b. 每次更新state: this.setSate()
componentWillUpdate() : 将要更新回调
render() : 更新(重新渲染)
componentDidUpdate() : 已经更新回调
c. 移除组件: ReactDOM.unmountComponentAtNode(containerDom)
componentWillUnmount() : 组件将要被移除回调
React全家桶(技术栈) - 图37

2.7.5. 重要的勾子

1) render(): 初始化渲染或更新渲染调用
2) componentDidMount(): 开启监听, 发送ajax请求
3) componentWillUnmount(): 做一些收尾工作, 如: 清理定时器
4) componentWillReceiveProps(): 后面需要时讲

2.8. 虚拟DOM与DOM Diff算法

2.8.1. 效果

React全家桶(技术栈) - 图38

| class HelloWorld extends React.Component { constructor(props) { super(props) this.state = { date: new Date() } } componentDidMount () { setInterval(() => { this.setState({ date: new Date() }) }, 1000) } render () { console.log(‘render()’) return ( <p> Hello, <input type=”text” placeholder=”Your name here”/>!  It is {this.state.date.toTimeString()} </p> ) } } ReactDOM.render( <HelloWorld/>, document.getElementById(‘example’) ) | | —- |


2.8.2. 基本原理图

React全家桶(技术栈) - 图39
DOM树:





他们都是普通的js对象,不是真实的DOM。

更新state,虚拟DOM就会重新更新。

更新state时,会重新创建虚拟DOM树,新/旧两树进行对比(DIFF算法发挥作用,算出来哪部分有变化),然后更新 差异对应的真实DOM,最终形成页面的局部重绘。

第3章:react应用(基于react脚手架)

3.1. 使用create-react-app创建react应用

3.1.1. react脚手架

真正去做react项目的时候,这个项目需要有一个比较大的环境,就是写一些配置,还有很多依赖。那么这种事情是通用的,既然是通用的,那么就会有专门的工具去帮我们去做,那么这种工具就叫做脚手架。
那么这个脚手架实际上是一个库,能帮我们创建一个空的项目,但其中项目的结构、配置、依赖都下载好了。
1) xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
a. 包含了所有需要的配置
b. 指定好了所有的依赖
c. 可以直接安装/编译/运行一个简单效果
2) react提供了一个用于创建react项目的脚手架库: create-react-app
create-react-app就是一个脚手架,在全局安装了这个包后,我们就可以使用这个”create-react-app”这个命令来创建一个项目模板。
3) 项目的整体技术架构为: react + webpack + es6 + eslint
4) 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化

3.1.2. 创建项目并启动

npm install -g create-react-app
create-react-app hello-react //hello-react__是项目(文件夹)名字
/ 上一行代码的意思是在终端当前目录下新建一个项目 /
上两行的代码,可以使用`npx create-react-app hello-react`代替,从而避免安装全局模块
cd hello-react
npm start

React全家桶(技术栈) - 图40

3.1.3. react脚手架项目结构

ReactNews
|—nodemodules—-第三方依赖模块文件夹
|—public / 这个文件夹主要放置html等静态资源 /
|— _index.html————————-主页面
|—scripts
|— build.js—————————-build打包引用配置
|— start.js—————————-start运行引用配置
|—src——————源码文件夹
|—components————————-react__组件
|—index.js—————————-应用入口js
|—.gitignore———git版本管制忽略的配置(设置哪些文件夹不需要git管理)
|—package.json——应用包配置文件
|—README.md———-应用描述说明的readme文件

其实所有的组件都可以写在src内的一个文件夹中,文件夹名为components。
组件的文件名一般都是小写(且用“-”连接)(但组件对象名首字母为大写),且后缀用jsx比较好,因为能与其他一般的模块区分,知道这是组件。


React全家桶(技术栈) - 图41
相当于
React全家桶(技术栈) - 图42

②所有的class都叫className
React全家桶(技术栈) - 图43


React全家桶(技术栈) - 图44
也可以这样
React全家桶(技术栈) - 图45


在用脚手架编写页面时,页面时实时更新的。
React全家桶(技术栈) - 图46
React全家桶(技术栈) - 图47(这里在你编辑后会闪烁一下,表示在更新数据)


因为这个react脚手架运用了webpack,所以应该有webpack.config.js,但这个文件没有暴露在项目目录外部,而是在这里↓
React全家桶(技术栈) - 图48


scripts这里的代码说明提供了四个命令(react scripts是cra提供的命令工具),
分别是:
start:启动项目
build:打包构建
test:运行测试代码
eject:暴露webpack配置(该指令不可逆)

在react中,默认情况下,webpack的配置是在react-scripts这个包里面的(上述第⑤点提到)。

React全家桶(技术栈) - 图49

3.2. demo: 评论管理

3.2.1. 效果

React全家桶(技术栈) - 图50

3.2.2. 拆分组件/提取出小组件

应用组件: App
state: comments/array
添加评论组件: CommentAdd
state: username/string, content/string
props: add/func
评论列表组件: CommentList
props: comment/object, delete/func, index/number
评论项组件: CommentItem
* props: comments/array, delete/func

3.2.3. 实现静态组件

3.2.4. 实现动态组件

动态展示初始化数据
初始化状态数据
传递属性数据
响应用户操作, 更新组件界面
绑定事件监听, 并处理
更新state

第4章:react ajax

4.1. 理解

4.1.1. 前置说明

  1. React本身只关注于界面, 并不包含发送ajax请求的代码;
  2. 前端应用需要通过ajax请求与后台进行交互(json数据);
  3. react应用中需要集成(即引用)第三方ajax库(或自己封装)。

    4.1.2. 常用的ajax请求库

  • jQuery: 比较重, 如果需要另外引入不建议使用。
  • axios: 轻量级, 建议使用
    • 封装XmlHttpRequest对象的ajax
    • promise风
    • 可以用在浏览器端和node服务器端
  • fetch: 原生函数, 但老版本浏览器不支持
axios.get(‘/user?ID=12345’)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

axios.get(‘/user’, {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});


2) POST请求

axios.post(‘/user’, {
firstName: ‘Fred’,
lastName: ‘Flintstone’
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});


4.3. Fetch

4.3.1. 文档

1) https://github.github.io/fetch/
2) https://segmentfault.com/a/1190000003810652

4.3.2. 相关API

1) GET请求

fetch(url).then(function(response) {
return response.json()
}).then(function(data) {
console.log(data)
}).catch(function(e) {
console.log(e)
});


2) POST请求

fetch(url, {
method: “POST”,
body: JSON.stringify(data),
}).then(function(data) {
console.log(data)
}).catch(function(e) {
console.log(e)
})


4.4. demo: github users

4.4.1. 效果

React全家桶(技术栈) - 图51

4.4.2. 拆分组件

App
state: searchName/string
Search
props: setSearchName/func
List
props: searchName/string
state: firstView/bool, loading/bool, users/array, errMsg/string

4.4.3. 编写静态组件

4.4.4. 编写动态组件

componentWillReceiveProps(nextProps): 监视接收到新的props, 发送ajax
使用axios库发送ajax请求

第5章:几个重要技术总结

5.1. 组件间通信

5.1.1. 方式一: 通过props传递

  1. 共同的数据放在父组件上, 特有的数据放在自己组件内部(state)
  2. 通过props可以传递一般数据和函数数据, 只能一层一层传递
  3. 一般数据—>父组件传递数据给子组件—>子组件读取数据
  4. 函数数据—>子组件传递数据给父组件—>子组件调用函数

子组件调用该函数时,其实可用于通知父组件干些事情
通过props来进行组件之间的通信,有两个弊端:无法直接地进行兄弟组件之间的通信与父组件和孙子组件的通信。前者需要利用父组件,后者需要一层一层地传递,总的来说就是不能一步到位 ``

5.1.2. 方式二: 使用消息订阅(subscribe)-发布(publish)机制

添加订阅相当于绑定了监听,好处是被订阅者一旦发布了新东西就马上能收到。
其他语言其实也有这种机制

  1. 工具库: PubSubJS
  2. 下载: npm install pubsub-js --save
  3. 使用:

    1. import PubSub from 'pubsub-js' //引入<br />`` 注意引入时顺序,要在引入其他组件的上面 ``<br /> PubSub.subscribe('delete', function(data){ }); //订阅<br /> PubSub.publish('delete', data) //发布消息

    5.1.3. 方式三: redux

    后面专门讲解

5.2. 事件监听理解

5.2.1. 原生DOM事件

  • 绑定事件监听
    • 事件名(类型): 只有有限的几个, 不能随便写
    • 回调函数
  • 触发事件

    • 用户操作界面
    • 事件名(类型)
    • 数据()

      5.2.2. 自定义事件(消息机制)

  • 绑定事件监听

    • 事件名(类型): 任意
    • 回调函数: 通过形参接收数据, 在函数体处理事件
  • 触发事件(编码)
    • 事件名(类型): 与绑定的事件监听的事件名一致
    • 数据: 会自动传递给回调函数


5.3. ES6常用新语法

1) 定义常量/变量: const/let
2) 解构赋值: let {a, b} = this.props import {aa} from ‘xxx’
3) 对象的简洁表达: {a, b}
4) 箭头函数:
a. 常用场景
组件的自定义方法: xxx = () => {}
参数匿名函数
b. 优点:
简洁
没有自己的this,使用引用this查找的是外部this
5) 扩展(三点)运算符: 拆解对象(const MyProps = {}, )
6) 类: class/extends/constructor/super
7) ES6模块化: export default | import

第6章:react-router4

6.1. 相关理解

6.1.1. react-router的理解

  1. react的一个插件库(基于react)
  2. 专门用来实现一个SPA应用
  3. 基于react的项目基本都会用到此库

    6.1.2. SPA的理解

  4. 单页Web应用(single page web application,SPA)

  5. 整个应用只有一个完整的页面(里面由各种组件构成)
  6. 点击页面中的链接不会刷新页面, 本身也不会向服务器发请求(这个链接称为路由链接)
  7. 当点击路由链接时, 只会做页面的局部更新(此时浏览器地址栏的地址也会变化)(路由跳转)
  8. 数据都需要通过ajax请求获取, 并在前端异步展现

    6.1.3. 路由的理解

  9. 什么是路由?

    1. 一个路由就是一个映射关系(key: value)
    2. key为路由路径(path), value可能是function或者component(前者为后端,后者为前端。因为前端此时就是用来更新显示各种组件而不是像后端一样处理请求然后响应数据)

路由器(router)和路由(route)是两个东西

  1. 路由分类
    1. 后台路由: node服务器端路由, value是function, 用来处理客户端提交的请求并返回一个响应数据
    2. 前台路由: 浏览器端路由, value是component, 当请求的是路由path时, 浏览器端前没有发送http请求, 但界面会更新显示对应的组件


  1. 后端路由
    1. 注册路由:router.get(path, function(req, res))
    2. 当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理前端发来的请求, 返回响应数据


  1. 前端路由
    1. 注册路由:
    2. 当浏览器的hash变为#about时, 当前路由组件就会变为About组件


6.1.4. 前端路由的实现

前端路由的底层实现原理↓,是靠history库

类似于栈的结构

  • history API
    • History.createBrowserHistory(): 得到封装window.history的管理对象
    • History.createHashHistory(): 得到封装window.location.hash的管理对象
    • history.push(): 添加一个新的历史记录
    • history.replace(): 用一个新的历史记录替换当前的记录
    • history.goBack(): 回退到上一个历史记录
    • history.goForword(): 前进到下一个历史记录
    • history.listen(function(location){}): 监视历史记录的变化

3) 测试
React全家桶(技术栈) - 图52 React全家桶(技术栈) - 图53

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>history test</title>
  6. </head>
  7. <body>
  8. <p><input type="text"></p>
  9. <a href="/test1" onclick="return push('/test1')">test1</a><br><br>
  10. <button onClick="push('/test2')">push test2</button><br><br>
  11. <button onClick="back()">回退</button><br><br>
  12. <button onClick="forword()">前进</button><br><br>
  13. <button onClick="replace('/test3')">replace test3</button><br><br>
  14. <script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
  15. <script type="text/javascript">
  16. let history = History.createBrowserHistory() // 方式一
  17. // history = History.createHashHistory() // 方式二
  18. // console.log(history)
  19. function push (to) {
  20. history.push(to)
  21. return false
  22. }
  23. function back() {
  24. history.goBack()
  25. }
  26. function forword() {
  27. history.goForward()
  28. }
  29. function replace (to) {
  30. history.replace(to)
  31. }
  32. history.listen((location) => {
  33. console.log('请求路由路径变化了', location)
  34. })
  35. </script>
  36. </body>
  37. </html>

上面创建历史记录有两种方式:
let history = History.createBrowserHistory() // 方式一

let history = History.createHashHistory() // 方式二
React全家桶(技术栈) - 图54(有井号)

6.2. react-router相关API

6.2.1. 组件

1) (对应上文讲的两种地址格式)
2) (对应上文讲的两种地址格式)
3)
4)
5)
6)
7)

6.2.2. 其它

1) history对象
2) match对象
3) withRouter函数
上面这些对象有各自的方法供我们使用

6.3. 基本路由使用

6.3.1. 效果

React全家桶(技术栈) - 图55

6.3.2. 准备

1) 下载react-router: npm install —save react-router@4
应该是下载web版本,所以是npm i react-router-dom
2) 引入bootstrap.css:

一旦有了路由,我们就有路由组件和非路由组件。
练习效果图:
React全家桶(技术栈) - 图56
三个组件、两个路由组件

6.3.3. 路由组件: views/about.jsx

| import React from ‘react’
export default function
About() {
return <div>About组件内容</div> } | | —- |

6.3.4. 路由组件: views/home.jsx

import React from ‘react’export default function About() {
return <div>Home组件内容</div>}

6.3.5. 包装NavLink组件: components/my-nav-link.jsx

  1. import React from 'react'
  2. import {NavLink} from 'react-router-dom'
  3. export default function MyNavLink(props) {
  4. return <NavLink {...props} activeClassName='activeClass'/>
  5. }

6.3.6. 应用组件: components/app.jsx

  1. import React from 'react'
  2. import {Route, Switch, Redirect} from 'react-router-dom'
  3. import MyNavLink from './components/my-nav-link'
  4. import About from './views/about'
  5. import Home from './views/home'
  6. export default class App extends React.Component {
  7. render () {
  8. return (
  9. <div>
  10. <div className="row">
  11. <div className="col-xs-offset-2 col-xs-8">
  12. <div className="page-header">
  13. <h2>React Router Demo</h2>
  14. </div>
  15. </div>
  16. </div>
  17. <div className="row">
  18. <div className="col-xs-2 col-xs-offset-2">
  19. <div className="list-group">
  20. {/*导航路由链接*/}
  21. <MyNavLink className="list-group-item" to='/about' >About</MyNavLink>
  22. <MyNavLink className="list-group-item" to='/home'>Home</MyNavLink>
  23. </div>
  24. </div>
  25. <div className="col-xs-6">
  26. <div className="panel">
  27. <div className="panel-body">
  28. {/*可切换的路由组件*/}
  29. <Switch>
  30. <Route path='/about' component={About} />
  31. <Route path='/home' component={Home} />
  32. <Redirect to='/about' />
  33. </Switch>
  34. </div>
  35. </div>
  36. </div>
  37. </div>
  38. </div>
  39. )
  40. }
  41. }

6.3.7. 自定义样式: index.css

| .activeClass {
color: red !important;
} | | —- |

6.3.8. 入口JS: index.js

  1. import React from 'react'
  2. import ReactDOM from 'react-dom'
  3. import {BrowserRouter, HashRouter} from 'react-router-dom'
  4. import App from './components/app'
  5. import './index.css'
  6. ReactDOM.render(
  7. (
  8. <BrowserRouter>
  9. <App />
  10. </BrowserRouter>
  11. /*<HashRouter>
  12. <App />
  13. </HashRouter>*/
  14. ),
  15. document.getElementById('root')
  16. )

6.4. 嵌套路由使用

路由组件中的路由称为嵌套路由。

6.4.1. 效果

React全家桶(技术栈) - 图57

6.4.2. 二级(子)路由组件: views/news.jsx

  1. import React from 'react'
  2. export default class News extends React.Component {
  3. state = {
  4. newsArr: ['news001', 'news002', 'news003']
  5. }
  6. render () {
  7. return (
  8. <div>
  9. <ul>
  10. {
  11. this.state.newsArr.map((news, index) => <li key={index}>{news}</li>)
  12. }
  13. </ul>
  14. </div>
  15. )
  16. }
  17. }

6.4.3. 二级路由组件: views/message.jsx

  1. import React from 'react'
  2. import {Link, Route} from 'react-router-dom'
  3. export default class Message extends React.Component {
  4. state = {
  5. messages: []
  6. }
  7. componentDidMount () {
  8. // 模拟发送ajax请求
  9. setTimeout(() => {
  10. const data = [
  11. {id: 1, title: 'Message001'},
  12. {id: 3, title: 'Message003'},
  13. {id: 6, title: 'Message006'},
  14. ]
  15. this.setState({
  16. messages: data
  17. })
  18. }, 1000)
  19. }
  20. render () {
  21. const path = this.props.match.path
  22. return (
  23. <div>
  24. <ul>
  25. {
  26. this.state.messages.map((m, index) => {
  27. return (
  28. <li key={index}>
  29. <Link to='???'>{m.title}</Link>
  30. </li>
  31. )
  32. })
  33. }
  34. </ul>
  35. </div>
  36. )
  37. }
  38. }

6.4.4. 一级路由组件: views/home.jsx

  1. import React from 'react'
  2. import {Switch, Route, Redirect} from 'react-router-dom'
  3. import MyNavLink from './components/my-nav-link'
  4. import News from './views/news'
  5. import Message from './views/message'
  6. export default function Home() {
  7. return (
  8. <div>
  9. <h2>Home组件内容</h2>
  10. <div>
  11. <ul className="nav nav-tabs">
  12. <li>
  13. <MyNavLink to='/home/news'>News</MyNavLink>
  14. </li>
  15. <li>
  16. <MyNavLink to="/home/message">Message</MyNavLink>
  17. </li>
  18. </ul>
  19. <Switch>
  20. <Route path='/home/news' component={News} />
  21. <Route path='/home/message' component={Message} />
  22. <Redirect to='/home/news'/>
  23. </Switch>
  24. </div>
  25. </div>
  26. )
  27. }

6.5. 向路由组件传递参数数据

一般的组件渲染时是写成组件标签的形式,但是路由组件不是写成组件标签的形式,而是用{}包裹着。即:
React全家桶(技术栈) - 图58
那就不能通过标签的属性来传递参数数据了。

6.5.1. 效果

React全家桶(技术栈) - 图59

6.5.2. 三级路由组件: views/message-detail.jsx

  1. import React from 'react'
  2. const messageDetails = [
  3. {id: 1, title: 'Message001', content: '我爱你, 中国'},
  4. {id: 3, title: 'Message003', content: '我爱你, 老婆'},
  5. {id: 6, title: 'Message006', content: '我爱你, 孩子'},
  6. ]
  7. export default function MessageDetail(props) {
  8. const id = props.match.params.id
  9. const md = messageDetails.find(md => md.id===id*1)
  10. return (
  11. <ul>
  12. <li>ID: {md.id}</li>
  13. <li>TITLE: {md.title}</li>
  14. <li>CONTENT: {md.content}</li>
  15. </ul>
  16. )
  17. }
  18. 之所以在params中有这个id
  19. 是因为
  20. params:是一个对象,保存了对应路径动态段的URL解析的键/值对

6.5.3. 二级路由组件: views/message.jsx

  1. import React from 'react'
  2. import {Link, Route} from 'react-router-dom'
  3. import MessageDetail from "./views/message-detail"
  4. export default class Message extends React.Component {
  5. state = {
  6. messages: []
  7. }
  8. componentDidMount () {
  9. // 模拟发送ajax请求
  10. setTimeout(() => {
  11. const data = [
  12. {id: 1, title: 'Message001'},
  13. {id: 3, title: 'Message003'},
  14. {id: 6, title: 'Message006'},
  15. ]
  16. this.setState({
  17. messages: data
  18. })
  19. }, 1000)
  20. }
  21. render () {
  22. const path = this.props.match.path
  23. return (
  24. <div>
  25. <ul>
  26. {
  27. this.state.messages.map((m, index) => {
  28. return (
  29. <li key={index}>
  30. <Link to={`${path}/${m.id}`}>{m.title}</Link>
  31. </li>
  32. )
  33. })
  34. }
  35. </ul>
  36. <hr/>
  37. <Route path={`${path}/:id`} component={MessageDetail}></Route>
  38. </div>
  39. )
  40. }
  41. }

即利用url后面加上“/:id”来传递参数数据。

React全家桶(技术栈) - 图60
注意该图下面还有一句话,会将:userId的值作为prop传递给组件UserProfile。

6.6. 多种路由跳转方式

是不是一定要用来跳转路由呢。

6.6.1. 效果

React全家桶(技术栈) - 图61

6.6.2. 二级路由: views/message.jsx

React全家桶(技术栈) - 图62

  1. import React from 'react'
  2. import {Link, Route} from 'react-router-dom'
  3. import MessageDetail from "./views/message-detail"
  4. export default class Message extends React.Component {
  5. state = {
  6. messages: []
  7. }
  8. componentDidMount () {
  9. // 模拟发送ajax请求
  10. setTimeout(() => {
  11. const data = [
  12. {id: 1, title: 'Message001'},
  13. {id: 3, title: 'Message003'},
  14. {id: 6, title: 'Message006'},
  15. ]
  16. this.setState({
  17. messages: data
  18. })
  19. }, 1000)
  20. }
  21. ShowDetail = (id) => {
  22. this.props.history.push(`/home/message/${id}`)
  23. }
  24. ShowDetail2 = (id) => {
  25. this.props.history.replace(`/home/message/${id}`)
  26. }
  27. back = () => {
  28. this.props.history.goBack()
  29. }
  30. forward = () => {
  31. this.props.history.goForward()
  32. }
  33. render () {
  34. const path = this.props.match.path
  35. return (
  36. <div>
  37. <ul>
  38. {
  39. this.state.messages.map((m, index) => {
  40. return (
  41. <li key={index}>
  42. <Link to={`${path}/${m.id}`}>{m.title}</Link>
  43. &nbsp;
  44. <button onClick={() => this.ShowDetail(m.id)}>查看详情(push)</button>&nbsp;
  45. <button onClick={() => this.ShowDetail2(m.id)}>查看详情(replace)</button>
  46. </li>
  47. )
  48. })
  49. }
  50. </ul>
  51. <p>
  52. <button onClick={this.back}>返回</button>&nbsp;
  53. <button onClick={this.forward}>前进</button>&nbsp;
  54. </p>
  55. <hr/>
  56. <Route path={`${path}/:id`} component={MessageDetail}></Route>
  57. </div>
  58. )
  59. }
  60. }



所用标签总结:
:译作浏览器路由器->包裹App标签
——这里也可用代替
:路由
:切换
:路由链接

第7章:react-ui

7.1. 最流行的开源React UI组件库

7.1.1. material-ui(国外)

官网: http://www.material-ui.com/#/

7.1.2. ant-design(国内蚂蚁金服)

1) PC官网: https://ant.design/index-cn
2) 移动官网: https://mobile.ant.design/index-cn

7.2. ant-design-mobile使用入门

7.2.1. 效果

image.png

7.2.2. 使用create-react-app创建react应用

  1. `npm install create-react-app -g`<br /> `create-react-app antm-demo`<br /> `cd antm-demo`<br /> `npm start`

7.2.3. 搭建antd-mobile的基本开发环境

  • 下载

npm install antd-mobile --save

  • src/App.jsx

    1. import React, {Component} from 'react'
    2. // 分别引入需要使用的组件
    3. import Button from 'antd-mobile/lib/button'
    4. import Toast from 'antd-mobile/lib/toast'
    5. export default class App extends Component {
    6. handleClick = () => {
    7. Toast.info('提交成功', 2)
    8. }
    9. render() {
    10. return (
    11. <div>
    12. <Button type="primary" onClick={this.handleClick}>提交</Button>
    13. </div>
    14. )
    15. }
    16. }
  • src/index.js ```jsx import React from ‘react’; import ReactDOM from ‘react-dom’ import App from “./App” // 引入整体css import ‘antd-mobile/dist/antd-mobile.css’

ReactDOM.render(, document.getElementById(‘root’))

  1. - index.html
  2. ```jsx
  3. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
  4. <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
  5. <script>
  6. if ('addEventListener' in document) {
  7. document.addEventListener('DOMContentLoaded', function() {
  8. FastClick.attach(document.body);
  9. }, false);
  10. }
  11. if(!window.Promise) {
  12. document.writeln('<script undefined'+'>'+'<'+'/'+'script>');
  13. }
  14. </script>

7.2.4. 实现按需打包(组件js/css)

按需打包是指 只打包对应的样式/js代码而不是整个样式/js文件。

  • 下载依赖包

yarn add react-app-rewired --dev
yarn add babel-plugin-import --dev

babel-plugin-import 是babel的一个插件,名为import,能使得只加载import所指定的模块。


  • 修改默认配置:

    • package.json

      1. "scripts": {
      2. "start": "react-app-rewired start",
      3. "build": "react-app-rewired build",
      4. "test": "react-app-rewired test --env=jsdom"
      5. }
    • config-overrides.js

      1. const {injectBabelPlugin} = require('react-app-rewired');
      2. module.exports = function override(config, env) {
      3. config = injectBabelPlugin(['import', {libraryName: 'antd-mobile', style: 'css'}], config);
      4. return config;
      5. };
  • 编码
    ```javascript // import ‘antd-mobile/dist/antd-mobile.css’

// import Button from ‘antd-mobile/lib/button’ // import Toast from ‘antd-mobile/lib/toast’ import {Button, Toast} from ‘antd-mobile’

  1. <a name="4W9ql"></a>
  2. # 第8章:redux
  3. <a name="lF7Dd"></a>
  4. ## 8.1. redux理解
  5. <a name="bHwff"></a>
  6. ### 8.1.1. 学习文档
  7. 1. 英文文档: [https://redux.js.org/](https://redux.js.org/)
  8. 2. 中文文档: [http://www.redux.org.cn/](http://www.redux.org.cn/)
  9. 3. Github: [https://github.com/reactjs/redux](https://github.com/reactjs/redux)
  10. <a name="z8ugh"></a>
  11. ### 8.1.2. redux是什么?
  12. 1. redux是一个独立专门用于做状态管理的JavaScript库(不是react插件库)。
  13. `` 也可以从它不是叫做react-redux,看出它不是react插件 ``
  14. 2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
  15. `` 因为redux与angular和vue配合使用会比较麻烦 ``
  16. 3. 作用:集中式地管理react应用中多个组件共享的状态。
  17. `` 像之前“评论管理”的项目,状态就是放在组件app.jsx内部从而集中管理的 ``<br />如果在大项目中,还集中地放在组件内部管理,就会使组件很大。所以那我们能不能集中地放在组件外部的一个地方来管理这些状态呢?
  18. <a name="ctK3s"></a>
  19. ### 8.1.3. redux工作流程
  20. ![](https://cdn.nlark.com/yuque/0/2020/png/2820033/1604374291799-73782b98-6b39-46b4-82cb-ea8f6e533df4.png#align=left&display=inline&height=263&margin=%5Bobject%20Object%5D&originHeight=420&originWidth=834&status=done&style=none&width=522)<br />(上图的上方三个框都是redux的代码)
  21. 其实一个组件,它要做两件事:展示数据;和用户交互从而更新数据。
  22. Store是redux里面最核心的一个对象。<br />
  23. <a name="ESm1y"></a>
  24. ### 8.1.4. 什么情况下需要使用redux
  25. 1. 总体原则:能不用就不用,如果不用比较吃力才考虑使用。(但现在一般做应用都会用)
  26. 2. 某个组件的状态,需要共享
  27. 3. 某个状态需要在任何地方都可以拿到
  28. 4. 一个组件需要改变全局状态
  29. 5. 一个组件需要改变另一个组件的状态
  30. <br />
  31. <a name="YoKnw"></a>
  32. ## 8.2. redux的核心API
  33. <a name="eDb7b"></a>
  34. ### 8.2.1. createStore()
  35. 作用:<br />创建包含指定reducer的store对象。<br />编码:<br />`import {createStore} from 'redux'`<br />`import counter from './reducers/counter'`<br />`const store = createStore(counter)`
  36. <a name="VnbIP"></a>
  37. ### 8.2.2. store对象
  38. 作用:<br />redux库最核心的管理对象<br />它内部维护着:<br />state<br />reducer<br />核心方法:<br />getState() ——获取状态显示<br />dispatch(action) ——分发action<br />subscribe(listener) ——订阅<br />编码:<br />store.getState()<br />store.dispatch({type:'INCREMENT', number})<br />dispatch()的参数传递一个action对象,一个广义行为。<br />store.subscribe(render)
  39. <a name="IlraW"></a>
  40. ### 8.2.3. applyMiddleware()
  41. 1) 作用:<br />应用上基于redux的中间件(插件库)<br />2) 编码:<br />import {createStore, applyMiddleware} from 'redux'<br />import thunk from 'redux-thunk' // redux异步中间件<br />const store = createStore(<br /> counter,<br /> applyMiddleware(thunk) // 应用上异步中间件<br />)
  42. <a name="pr11W"></a>
  43. ### 8.2.4. combineReducers()
  44. - 作用:
  45. 合并多个reducer函数
  46. - 编码:
  47. export default combineReducers({<br /> user,<br /> chatUser,<br /> chat<br />})<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/2820033/1608464256809-bf2ef2e7-a9db-4eaf-817e-4b1124a7efa3.png#align=left&display=inline&height=263&margin=%5Bobject%20Object%5D&name=image.png&originHeight=416&originWidth=575&size=70037&status=done&style=shadow&width=363)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/2820033/1608464414314-3b0cac2f-5919-4e4d-8323-cc3504f0c9e5.png#align=left&display=inline&height=101&margin=%5Bobject%20Object%5D&name=image.png&originHeight=201&originWidth=1001&size=55625&status=done&style=shadow&width=500.5)<br />App组件中↓:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/2820033/1608464542869-78710e39-231f-4e80-9c27-34970fa86abc.png#align=left&display=inline&height=96&margin=%5Bobject%20Object%5D&name=image.png&originHeight=130&originWidth=500&size=29101&status=done&style=shadow&width=369)<br />store.js中↓:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/2820033/1608466004479-214fa5d3-b199-4f13-8123-68d0035f3658.png#align=left&display=inline&height=134&margin=%5Bobject%20Object%5D&name=image.png&originHeight=235&originWidth=942&size=58816&status=done&style=shadow&width=536)<br />以前的store.js是↓:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/2820033/1608466068032-4a13d929-0666-4c9a-bfa3-3d0990b302c8.png#align=left&display=inline&height=132&margin=%5Bobject%20Object%5D&name=image.png&originHeight=219&originWidth=909&size=42796&status=done&style=none&width=548)
  48. <a name="aguZb"></a>
  49. ## 8.3. redux的三个核心概念
  50. store是一个对象;<br />action是一个对象;<br />reducer是一个函数。
  51. <a name="uwz04"></a>
  52. ### 8.3.1. action
  53. - 标识要执行行为的对象
  54. `console.log(action)`
  55. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2820033/1605155120491-8aa50e11-fcc8-4670-a9af-b0cc1e0890f5.png#align=left&display=inline&height=52&margin=%5Bobject%20Object%5D&name=image.png&originHeight=64&originWidth=297&size=4730&status=done&style=none&width=243)
  56. - 包含2个方面的属性
  57. - type:标识属性,值为字符串,唯一, 必要属性。
  58. - xxx:数据属性,值类型任意, 可选属性。
  59. - 该数据是通过react组件中调用store的dispatch方法传入到reducer函数中。
  60. - 例子:
  61. ```javascript
  62. const action = {
  63. type: 'INCREMENT',
  64. data: 2
  65. }


  • Action Creator(创建Action的工厂函数)

const increment = (number) => ({type: 'INCREMENT', data: number})
这里箭头后面是小括号,所以是返回了一个对象。

8.3.2. reducer

  • reducer函数是根据旧state和action而产生新state的纯函数。
  • 样例

    1. export default function counter(state = 0, action) {
    2. switch (action.type) {
    3. case 'INCREMENT':
    4. return state + action.data
    5. case 'DECREMENT':
    6. return state - action.data
    7. default:
    8. return state
    9. }
    10. }
  • 注意

    • 返回一个新的state
    • 不要修改原来的状态(即使用的是this.setState())

      8.3.3. store

  • 将state,action与reducer联系在一起的管理对象。

  • 如何得到此对象?

    1. `import {createStore} from 'redux'`<br /> `import reducer from './reducers'`<br /> `const store = createStore(reducer)`<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/2820033/1605163100379-c381e918-7a18-4913-b684-1e42f540ee4f.png#align=left&display=inline&height=110&margin=%5Bobject%20Object%5D&name=image.png&originHeight=146&originWidth=612&size=14117&status=done&style=none&width=459)
  • 此对象的功能?

    1. `getState()`: 得到state<br /> `dispatch(action)`: 分发action, 触发reducer调用, 产生新的state<br /> `subscribe(listener)`: 注册监听, 当产生了新的state时, 自动调用

    8.4. 使用redux编写应用

    8.4.1. 效果

    image.png

    8.4.2. 下载依赖包

    npm install --save redux

    8.4.3. redux/action-types.js

  1. /*
  2. action对象的type常量名称模块
  3. */
  4. export const INCREMENT = 'increment'
  5. export const DECREMENT = 'decrement'

8.4.4. redux/actions.js

  1. /*
  2. action creator模块
  3. */
  4. import {INCREMENT, DECREMENT} from './action-types'
  5. export const increment = number => ({type: INCREMENT, number})
  6. export const decrement = number => ({type: DECREMENT, number})

8.4.5. redux/reducers.js

  1. /*
  2. 根据老的state和指定action, 处理返回一个新的state
  3. */
  4. import {INCREMENT, DECREMENT} from '../constants/ActionTypes'
  5. import {INCREMENT, DECREMENT} from './action-types'
  6. export function counter(state = 0, action) {
  7. console.log('counter', state, action)
  8. switch (action.type) {
  9. case INCREMENT:
  10. return state + action.number
  11. case DECREMENT:
  12. return state - action.number
  13. default:
  14. return state
  15. }
  16. }

8.4.6. components/app.jsx

  1. /*
  2. 应用组件
  3. */
  4. import React, {Component} from 'react'
  5. import PropTypes from 'prop-types'
  6. import * as actions from '../redux/actions'
  7. export default class App extends Component {
  8. static propTypes = {
  9. store: PropTypes.object.isRequired,
  10. }
  11. increment = () => {
  12. const number = this.refs.numSelect.value * 1
  13. this.props.store.dispatch(actions.increment(number))
  14. }
  15. decrement = () => {
  16. const number = this.refs.numSelect.value * 1
  17. this.props.store.dispatch(actions.decrement(number))
  18. }
  19. incrementIfOdd = () => {
  20. const number = this.refs.numSelect.value * 1
  21. let count = this.props.store.getState()
  22. if (count % 2 === 1) {
  23. this.props.store.dispatch(actions.increment(number))
  24. }
  25. }
  26. incrementAsync = () => {
  27. const number = this.refs.numSelect.value * 1
  28. setTimeout(() => {
  29. this.props.store.dispatch(actions.increment(number))
  30. }, 1000)
  31. }
  32. render() {
  33. return (
  34. <div>
  35. <p>
  36. click {this.props.store.getState()} times {' '}
  37. </p>
  38. <select ref="numSelect">
  39. <option value="1">1</option>
  40. <option value="2">2</option>
  41. <option value="3">3</option>
  42. </select>{' '}
  43. <button onClick={this.increment}>+</button>
  44. {' '}
  45. <button onClick={this.decrement}>-</button>
  46. {' '}
  47. <button onClick={this.incrementIfOdd}>increment if odd</button>
  48. {' '}
  49. <button onClick={this.incrementAsync}>increment async</button>
  50. </div>
  51. )
  52. }
  53. }

8.4.7. index.js

  1. import React from 'react'
  2. import ReactDOM from 'react-dom'
  3. import {createStore} from 'redux'
  4. import App from './components/app'
  5. import {counter} from './redux/reducers'
  6. // 根据counter函数创建store对象
  7. const store = createStore(counter)
  8. // 定义渲染根组件标签的函数
  9. const render = () => {
  10. ReactDOM.render(
  11. <App store={store}/>,
  12. document.getElementById('root')
  13. )
  14. }
  15. // 初始化渲染
  16. render()
  17. // 注册(订阅)监听, 一旦状态发生改变, 自动重新渲染
  18. store.subscribe(render)

8.4.8. 问题

  1. redux与react组件的代码耦合度太高
  2. 编码不够简洁

    8.5. react-redux

    8.5.1. 理解

  3. 一个react插件库

  4. 专门用来简化react应用中使用redux

    8.5.2. React-Redux将所有组件分成两大类

  • UI组件
    • 只负责 UI 的呈现,不带有任何业务逻辑
    • 通过props接收数据(一般数据和函数)
    • 不使用任何 Redux 的 API
    • 一般保存在components文件夹下
  • 容器组件

    • 负责管理数据和业务逻辑,不负责UI的呈现
    • 使用 Redux 的 API
    • 一般保存在containers文件夹下

      image.png

      8.5.3. 相关API

  1. Provider

让所有组件都可以得到state数据

  1. <Provider store={store}>
  2. <App />
  3. </Provider>
  1. connect()

用于包装 UI 组件生成容器组件

  1. import { connect } from 'react-redux'
  2. connect(
  3. mapStateToprops, //把状态映射为属性,该参数是一个函数类型,但返回的是一个对象。
  4. mapDispatchToProps //是一个对象。该对象包含了一些action,需要从actions.js里引入。
  5. )(Counter)
  1. mapStateToprops()

将外部的数据(即state对象)转换为UI组件的标签属性

  1. const mapStateToprops = function (state) {
  2. return {
  3. value: state
  4. }
  5. }
  1. mapDispatchToProps()

将分发action的函数转换为UI组件的标签属性
简洁语法可以直接指定为actions对象或包含多个action方法的对象

8.5.4. 使用react-redux

1) 下载依赖包
npm install --save react-redux
2) redux/action-types.js
不变
3) redux/actions.js
不变
4) redux/reducers.js
不变
5) components/counter.jsx(UI组件)

  1. /*
  2. UI组件: 不包含任何redux API
  3. */
  4. import React from 'react'
  5. import PropTypes from 'prop-types'
  6. export default class Counter extends React.Component {
  7. static propTypes = {
  8. count: PropTypes.number.isRequired,
  9. increment: PropTypes.func.isRequired,
  10. decrement: PropTypes.func.isRequired
  11. }
  12. increment = () => {
  13. const number = this.refs.numSelect.value * 1
  14. this.props.increment(number)
  15. }
  16. decrement = () => {
  17. const number = this.refs.numSelect.value * 1
  18. this.props.decrement(number)
  19. }
  20. incrementIfOdd = () => {
  21. const number = this.refs.numSelect.value * 1
  22. let count = this.props.count
  23. if (count % 2 === 1) {
  24. this.props.increment(number)
  25. }
  26. }
  27. incrementAsync = () => {
  28. const number = this.refs.numSelect.value * 1
  29. setTimeout(() => {
  30. this.props.increment(number)
  31. }, 1000)
  32. }
  33. render() {
  34. return (
  35. <div>
  36. <p>
  37. click {this.props.count} times {' '}
  38. </p>
  39. <select ref="numSelect">
  40. <option value="1">1</option>
  41. <option value="2">2</option>
  42. <option value="3">3</option>
  43. </select>{' '}
  44. <button onClick={this.increment}>+</button>
  45. {' '}
  46. <button onClick={this.decrement}>-</button>
  47. {' '}
  48. <button onClick={this.incrementIfOdd}>increment if odd</button>
  49. {' '}
  50. <button onClick={this.incrementAsync}>increment async</button>
  51. </div>
  52. )
  53. }
  54. }

6) App.jsx(容器组件)containters/app.jsx

  1. /*
  2. 包含Counter组件的容器组件
  3. */
  4. import React from 'react'
  5. // 引入连接函数
  6. import {connect} from 'react-redux'
  7. // 引入action函数
  8. import {increment, decrement} from '../redux/actions'
  9. import Counter from '../components/counter'
  10. // 向外暴露连接App组件的包装组件
  11. export default connect(
  12. state => ({count: state}),
  13. {increment, decrement}
  14. )(Counter)

7) index.js

  1. import React from 'react'
  2. import ReactDOM from 'react-dom'
  3. import {createStore} from 'redux'
  4. import {Provider} from 'react-redux'
  5. import App from './containers/app'
  6. import {counter} from './redux/reducers'
  7. // 根据counter函数创建store对象
  8. const store = createStore(counter)
  9. // 定义渲染根组件标签的函数
  10. ReactDOM.render(
  11. (
  12. <Provider store={store}>
  13. <App />
  14. </Provider>
  15. ),
  16. document.getElementById('root')

image.png(index.js)
image.png

image.png
原App组件标签中,自动添加了属性(props)。
image.png
所以UI组件counter.jsx里的主体部分就不用再写redux代码了。

8.5.5. 问题

  1. redux默认是不能进行异步处理的,
  2. 应用中又需要在redux中执行异步任务(ajax, 定时器)

不太懂为什么不能在react执行异步任务,要在redux中执行异步任务

8.6. redux异步编程

8.6.1. 下载redux插件(异步中间件)

npm install --save redux-thunk
中间件:
中间件一般是叫谁谁谁(库)的中间件,比如说express。其实express本身实现的功能很少,它是通过很多的中间件来实现很多功能,说白了就是能扩展当前库功能的插件。

8.6.2. index.js

  1. import {createStore, applyMiddleware} from 'redux'
  2. import thunk from 'redux-thunk'
  3. // 根据counter函数创建store对象
  4. const store = createStore(
  5. counter,
  6. applyMiddleware(thunk) // 应用上异步中间件
  7. )

8.6.3. redux/actions.js

  1. // 异步action creator(返回一个函数)
  2. export const incrementAsync = number => {
  3. return dispatch => {
  4. setTimeout(() => {
  5. dispatch(increment(number))
  6. }, 1000)
  7. }
  8. }

8.6.4. components/counter.jsx

  1. incrementAsync = () => {
  2. const number = this.refs.numSelect.value*1
  3. this.props.incrementAsync(number)
  4. }

8.6.5. containers/app.jsx

  1. import {increment, decrement, incrementAsync} from '../redux/actions'
  2. // 向外暴露连接App组件的包装组件export default connect(
  3. state => ({count: state}),/* state是返回reducers.js的代表某状态的reducer函数,该函数返回的是更新后的状态值 */
  4. {increment, decrement, incrementAsync}
  5. )(Counter)

8.7. 使用上redux调试工具

现在所有状态都交给redux管理,react是自然看不到具体的状态情况了,那我想通过工具查看具体的状态管理情况怎么看呢?利用调试工具和工具依赖的包。

8.7.1. 安装chrome浏览器插件

React全家桶(技术栈) - 图70

8.7.2. 下载工具依赖包

npm install --save-dev redux-devtools-extension

8.7.3. 编码

  1. import { composeWithDevTools } from 'redux-devtools-extension'
  2. const store = createStore(
  3. counter,
  4. composeWithDevTools(applyMiddleware(thunk))
  5. )

8.8. 相关重要知识: 纯函数和高阶函数

8.8.1. 纯函数

  • 一类特别的函数: 只要是同样的输入,必定得到同样的输出
  • 必须遵守以下一些约束
    • 不得改写参数
    • 不能调用系统 I/O 的API
    • 能调用Date.now()或者Math.random()等不纯的方法
  • reducer函数必须是一个纯函数

    8.8.2. 高阶函数

  • 理解: 一类特别的函数

    • 情况1: 参数是函数
    • 情况2: 返回是函数lai
  • 常见的高阶函数:
    • 定时器设置函数
    • 数组的map()/filter()/reduce()/find()/bind()
    • react-redux中的connect函数
  • 作用:

    • 能实现更加动态, 更加可扩展的功能