今日目标
✔ 掌握常见的组件通讯的三种方式(父子、子父、兄弟)。
✔ 掌握通过 Context 实现跨层级通讯。
✔ 完成评论列表案例。
代码片段插件
ES7 React/Redux/GraphQL/React-Native snippets
组件通讯概述
目标
了解为什么需要组件通讯?

内容
组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。
在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的管理整个应用的功能。
而在这个过程中,多个组件之间不可避免的要传递或共享某些数据。
为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通,这个过程就是组件通讯。
组件通信常见的方式有:父传子、子传父、兄弟相传、跨组件通信等。
总结
组件通信的目的是什么?
父传子
目标
掌握如何将父组件的数据,传递给子组件。
需求

内容
- 父组件(使用组件的地方)通过自定义属性提供数据。
src/Parent.js
1 | import React, { Component } from 'react' |
- 子组件(定义组件的地方)通过 this.props/props 接收。
类组件:通过 this.props
接收。
src/Child.js
1 | import React, { Component } from 'react' |
函数组件:通过 props
接收。
src/Child.js
1 | const Child = (props) => { |
小结
父传子的 2 个步骤
父组件通过 __ 提供数据?
子组件,类组件通过 __ 接收数据,函数组件通过 __ 接收数据?
注意事项
目标
掌握 props 的注意点。
知道什么是单向数据流。
内容
Props 是只读的,不能修改。
单向数据流,也叫做:自上而下的数据流。
a,当父组件中的数据更新时,子组件接收到的数据也会自动更新。
b,但不能反过来,例如子组件直接去修改父组件的数据。
c,类比:就像瀑布的水一样只能从上往下流动,并且,当上游的水变浑浊,下游的水也会受到影响。
可以给组件传递任意类型的数据,例如数字、字符串、布尔、对象、函数、JSX 等。
使用类组件时,如果写了构造函数,应该在 constructor 中接收 props,并将 props 传递给 super,否则无法在构造函数中使用 this.props。
举例
修改会报错
1 | import React, { Component } from 'react' |
小结
props 可以被修改吗?
什么是单项数据流?
父传子 📝
目标

准备父组件
数据、结构、样式
components/Parent/index.js
1 | import React, { Component } from 'react' |
components/Parent/index.css
1 | .parent { |
准备子组件
结构、样式
components/Child/index.js
1 | import React, { Component } from 'react' |
components/Child/index.css
1 | .child { |
传递数据
components/Parent/index.js
1 | import React, { Component } from 'react' |
components/Child/index.js
1 | import React, { Component } from 'react' |
完整代码
components/Parent/index.js
1 | import React, { Component } from 'react' |
components/Child/index.js
1 | import React, { Component } from 'react' |
子传父
目标
能够将子组件的数据传递给父组件。
内容

a,父组件通过属性传递一个回调函数。
b,子组件调用传递过来的回调函数,并将要传递的数据作为回调函数的实参。
c,父组件在回调函数中通过形参接收传递过来的数据并做相应操作。
App.jsx
1 | import React from 'react' |
Hello.jsx
1 | // #2 子组件调用传递过来的回调函数并传参 |
小结
子传父的流程是什么?
子传父 📝
目标

思路
a,父组件通过属性传递一个回调函数。
b,子组件调用传递过来的回调函数,并将要传递的数据作为回调函数的实参。
c,父组件在回调函数中通过形参接收传递过来的数据并做相应操作。
砍价
components/Child/index.js
1 | import React, { Component } from 'react' |
components/Parent/index.js
1 | import React, { Component } from 'react' |
细节
保留 2 位小数,加个为负的处理。
components/Parent/index.js
1 | class Parent extends Component { |
完整
components/Parent/index.js
1 | import React, { Component } from 'react' |
components/Child/index.js
1 | import React, { Component } from 'react' |
兄弟通信
目标
能够明白兄弟组件间的数据通信流程。
内容
需求:点击 A 中的按钮,修改 B 中的数据 count。

步骤
准备 A、B 兄弟组件。
把需要操作的 B 组件中的数据 count 提升到公共的父组件里面。
父组件提供数据和操作数据的方法。
把数据传递给 B 组件,把操作数据的方法传递给 A 组件。
App.jsx
1 | import React, { Component } from 'react' |
A.jsx
1 | import React, { Component } from 'react' |
B.jsx
1 | import React, { Component } from 'react' |
总结
什么是状态提升?
Context
目标
通过 Context 实现跨级组件通讯。
内容
之前通信的局限性。
远房亲戚关系(也就是两个组件之间间隔较远),可以使用 Context。
步骤

祖先组件通过
React.createContext()
创建 Context 并导出。祖先组件通过
<Context.Provider>
配合 value 属性提供数据。后代组件通过
<Context.Consumer>
配合函数获取数据。优化:提取
React.createContext()
到单独的文件里面。
代码
App.jsx
1 | import React, { Component, createContext } from 'react' |
A.jsx
1 | import React, { Component } from 'react' |
B.jsx
1 | import React, { Component } from 'react' |
另一种获取数据的方式
1 | import React, { Component } from 'react' |
指定默认值
注意默认值生效的条件:并不是不传递 value,而是没有找到包裹 Context.Provider 的祖先元素
1 | import React from 'react' |
总结
跨层级组件通信的步骤是什么?
B 站评论列表 📝
案例目标

组件拆分
App.jsx 拆分前
把下面静态结构提取到 App.jsx
中,拆分 Tabs.jsx
、Form.jsx
、List.jsx
组件到 components 文件夹下。
1 | import React, { Component } from 'react' |
App.jsx 拆分后
1 | import React, { Component } from 'react' |
Tabs.jsx
1 | import React, { Component } from 'react' |
Form.jsx
1 | import React, { Component } from 'react' |
List.jsx
1 | import React, { Component } from 'react' |
index.css
1 | * { |
渲染 Tabs
需求:渲染 tabs 数据和默认高亮状态的处理。
把父组件的 tabs 数据传递给 Tabs 组件。
Tabs 组件循环传递过来的数据。
把父组件的 active 传递给 Tabs 组件。
Tabs 组件内根据传递过来的 active 和循环时候的 item.type 进行比较,如果一致就使用 on class。
1 | export default class App extends Component { |
Tabs 切换
父组件准备一个操作数据的方法(修改 active),并传递给 Tabs 组件。
点击 Tabs 调用传递过来的的方法,并传递当前点击项的 type。
在父组件的方法内根据传递过来的 type 修改 active。
App.jsx
1 | import React, { Component } from 'react' |
components/Tabs.jsx
1 | import React, { Component } from 'react' |
列表展示
把父组件的数据传递到 List 组件。
List 组件接收数据并通过 map 进行遍历。
App.jsx
1 | export default class App extends Component { |
components/List.jsx
1 | import React, { Component } from 'react' |
时间处理
初始时间数据都变成一个对象。
输出的时候可以通过 dayjs 进行格式化。
1 | import dayjs from 'dayjs' |
classnames
1 | yarn add classnames |
components/List.jsx
1 | import React, { Component } from 'react' |
排序功能
把父组件的 active 变量传递到 List 组件。
List 组件根据 active 是 hot 或 time 进行对应的排序。
渲染排序完之后的数据。
components/List.jsx
1 | import React, { Component } from 'react' |
添加评论
父组件准备一个操作数据的方法(修改 list 数组),并传递给 Form 组件。
Form 组件调用传递过来的方法,并传递过去收集到的数据(通过受控表单组件收集数据)。
父组件的方法通过形参接收传递过来的数据,并加工成一个评论对象。
把评论对象添加到 list 数组的前面。
App.jsx
1 | import React, { Component } from 'react' |
components/Form.jsx
1 | import React, { Component } from 'react' |
删除评论
父组件准备一个操作数据的方法(删除评论),并把这个方法传递给 List 组件。
点击 List 组件中的删除按钮的时候调用传递过来的方法,并传递过去评论 id。
父组件根据 id 来删除 list 数组中的数据。
App.jsx
1 | import React, { Component } from 'react' |
List.jsx
1 | import React, { Component } from 'react' |
点赞评论
父组件准备一个操作数据的方法,并把这个方法传递给 List 组件。
点击 List 组件中的点赞/踩按钮的时候调用传递过来的方法,并传递过去评论 id 和点赞的状态。
父组件根据 id 和传递过来的点赞状态来修改数据。
App.jsx
1 | import React, { Component } from 'react' |
List.jsx
1 | import React, { Component } from 'react' |
完整代码
App.jsx
1 | import React, { Component } from 'react' |
components/Tabs.jsx
1 | import React, { Component } from 'react' |
components/Form.jsx
1 | import React, { Component } from 'react' |
components/List.jsx
1 | import React, { Component } from 'react' |