1 组件通信的案例
组件与组件之间需要不断的通信进行沟通,保证数据传递。父子组件、兄弟组件、祖先组件之间都是需要进行通信,修改状态,数据交互等等。
1.1 父组件向子组件进行通信
父组件向子组件通信一般都是父组件通过子组件的标签上添加属性完成数据的传递。在子组件中,我们需要的数据都被react内部保存在this.props属性对象上面了,我们只需要访问this.props属性即可。
父组件在展示子组件,可能会传递一些数据给子组件
- 父组件通过属性=值的形式来传递给子组件数据
- 子组件通过props参数获取父组件传递过来的数据
```javascript
// 父子组件通信案例
// 父组件
export default class App extends React.Component {
render() {
// 定义我们需要传递的数据 该数据一般是从网络中获取的
const hobby = [“吃饭”, “睡觉”, “打豆豆”]
} }<div>
<p>父组件</p>
// 子组件 父组件通过组件标签以属性的方式向子组件传递数据
<Child name="coderweiwei" age={123} hobby={ hobby } />
// 子组件 export default class Child extends Component { render() { // 对父传递的数据进行解析 const { name, age, hobby } = this.props
子组件
姓名:{ name }
年龄:{ age }
爱好:对父组件传递的数据进行循环渲染
-
{
hobby.map((item, index) => {
return
- { item } }) }
<a name="n0TdC"></a>
## 1.2 子组件向父组件通信
> 子组件向父组件通信原理:在组件中进行数据传递的时候,我们的数据是单向传递的,是自上而下的传递方式,只能由父组件流向子组件,子组件不能对父组件传递的数据进行更改,如果子组件可以修改我们的传递的数据,每个组件都会修改父组件的数据,那么我们父组件的状态数据将会变得不可控。所以,子组件向父组件通过子事件的事件来触发父组件相应的函数,父组件自己修改数据,就会避免发生上述问题。
某些情况,我们也需要子组件向父组件传递消息:
- 在vue中是通过自定义事件来完成的
- 在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可
```javascript
// 子组件通过事件的方式触发父组件的相关事件
原理:父组件通过标签的属性向子组件传递属性和方法,那么我们的子组件就可以直接调用父组件传递过来的方法,并且可以携带相应的参数 来触发父组件的方法。这里不再进行赘述。
1.3 组件通信Tab选项卡切换案例
// 父组件与子组件、子组件与父组件通信
import React, { PureComponent } from 'react'
// 引入子组件
import NavBar from './NavBar'
import Content from './Content'
export default class App extends PureComponent {
constructor() {
super()
// 一般不需要改动的数据都放在组件的实例对象this中
this.titleList = ['流行', '新款', '精选']
this.state = {
index: 0
}
}
render() {
return (
<div style={{ backgroundColor: '#ccc'}}>
{/* 传递给子组件的属性和方法 子组件可以直接触发该方法 让父组件来修改数据 */}
<NavBar titleList={ this.titleList } btnClick={ index => this.itemClick(index) }/>
<Content content={ this.titleList[this.state.index] }/>
</div>
)
}
itemClick(order) {
this.setState({
index: order
})
}
}
// NavBar组件
import React, { Component } from 'react'
// 引入样式
import './nav.css'
export default class NavBar extends Component {
constructor() {
super()
this.state = {
currentIndex: 0
}
}
render() {
return (
<div className="nav">
<ul>
{
this.props.titleList.map((item, index) => {
return <li
className={ index === this.state.currentIndex ? 'active' : ''}
key={ item }
onClick={ () => this.itemClick(index) }>{ item }
</li>
})
}
</ul>
</div>
)
}
itemClick(index) {
// 修改当前的index值
this.setState({
currentIndex: index
})
// 触发父组件传递过来的方法
this.props.btnClick(index)
}
}
// 子组件2
import React, { Component } from 'react'
export default class Content extends Component {
render() {
return (
<div>
<p>父组件传递的内容-主要的作用根据父组件传递的值来决定组件里面数据的渲染-组件内的数据是父组件传递过来的</p>
<h2>{ this.props.content }</h2>
</div>
)
}
}
1.4 组件通信-插槽案例的演示
插槽一词最早起源于vue中,就是在组件进行封装的时候,我们并不知道组件在使用的时,父组件传递的数据是那样的,具体是什么结构,由传递数据的组件决定,最开始预留一定的接口,以便在后期的使用中传入数据,来决定子组件的结构是什么。
案例演示1
// 通过组件的双标签,向组件的双标签中插入需要穿的的数据
// 先引入子组件
import NavBar from "./NavBar"
// 父组件
export default class App extends Component {
render() {
return (
<div>
<p>父组件</p>
// 子组件 向标签体里面插入DOM的结构
<NavBar>
<div>左边-结构1</div>
<div>中间-结构2</div>
<div>右边-结构3</div>
</NavBar>
</div>
)
}
}
// 子组件
export default class NavBar extends Component {
render() {
// 获取上面的结构 在this.props.children中进行获取
return (
<div className="child-container">
<div className="nav-left">
{ this.props.children[0] }
</div>
<div className="nav-center">
{ this.props.children[1] }
</div>
<div className="nav-right">
{ this.props.children[2] }
</div>
</div>
)
}
}
案例演示2-比较常用
// 原理:利用组件的标签属性 传递jsx的结构
// 父组件
export default class App extends Component {
render() {
return (
<div>
<p>这是父组件页面</p>
// 通过组件标签的属性来传递数据
<NavBar
leftSlot={ <div>左边-结构1</div> }
centerSlot={ <div>中间-结构1</div> }
rightSlot={ <div>右边-结构1</div> }
/>
</div>
)
}
}
// 子组件
export default class NavBar extends Component {
render() {
// 对父组件传递的数据进行解构赋值
const { leftSlot, centerSlot, rightSlot } = this.props
return (
<div className="nav-bar">
<div class="nav-left">{ leftSlot }</div>
<div class="nav-center">{ centerSlot }</div>
<div class="nav-right">{ rightSlot }</div>
</div>
)
}
}
2 非父子组件数据共享
2.1 通过props实现数据的传递
import React, { Component } from 'react'
// 根组件
export default class App extends Component {
constructor() {
super()
this.state = {
name: "coderweiwei",
age: 18,
toDoList: ["吃饭", "睡觉", "打豆豆"]
}
}
render() {
return (
<div>
<p>app组件</p>
{/* <Parent movie={ this.state.movie }/> */}
<hr/>
// 向子组件进行数据的传递
<Parent {...this.state }/>
</div>
)
}
}
// 父组件
class Parent extends Component {
render() {
return (
<div>
<p>我是父组件</p>
<Son { ...this.props } />
</div>
)
}
}
// 子组件
class Son extends Component {
render() {
// 可以看出传递的数据
console.log(this.props);
return (
<div>
<p>我是子组件</p>
<ul>
{
this.props.toDoList.map((item, index) => {
return <li key={ index }>{ item }</li>
})
}
</ul>
</div>
)
}
}
2.2 通过Context来传递数据
原理:
非父子组件数据的共享:
- 在开发中,比较常见的数据传递方式是通过props属性自上而下(由父到子)进行传递。
- 但是对于有一些场景:比如一些数据需要在多个组件中进行共享(地区偏好、UI主题、用户登录状态、用户信息等)。
- 如果我们在顶层的App中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的操作。
但是,如果层级更多的话,一层层传递是非常麻烦,并且代码是非常冗余的:
- React提供了一个API:Context;
- Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递props;
- Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;
Context相关API:
- React.createContext
- 需要创建一个共享的context上下文对象
- 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的Provider中读取到当前的context值
- defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
- Context.Provider
- 每个Context 对象都会返回一个Provider React 组件,它允许消费组件订阅context 的变化
- Provider接收一个value属性,传递给消费组件
- 一个Provider可以和多个消费组件有对应关系
- 多个Provider也可以嵌套使用,里层的会覆盖外层的数据
- 当Provider的value值发生变化时,它内部的所有消费组件都会重新渲染
- Class.contextType
- 挂载在class 上的contextType属性会被重赋值为一个由React.createContext()创建的Context 对象
- 这能让你使用this.context来消费最近Context 上的那个值;
- 你可以在任何生命周期中访问到它,包括render函数中
- Context.Consumer
- 这里,React 组件也可以订阅到context 变更。这能让你在函数式组件中完成订阅context。
- 这里需要函数作为子元素(function as child)这种做法
- 这个函数接收当前的context 值,返回一个React 节点 ```javascript import React, { Component } from ‘react’
// 创建context对象 参数是默认的初始化对象 // context组件命名的时候 最好是大写的形式 因为最后会被映射为组件标签 const UserContext = React.createContext({ nickname: “默认的姓名”, level: 123456 })
export default class App extends Component { constructor(props) { super(props) this.state = { nickname: “weiwei”, level: 18 } } render() { return (
根组件的状态数据
{ this.state.nickname }
{ this.state.level }
{/ 需要传递数据的根组U件 将我们需要传递数据的组件 置于该组件中 需要传递的数据当作value属性的属性值 /}// 父组件 class Parent extends Component { render() { console.log(“parent”, this.context); return (
父组件
{ this.context.nickname }
{ this.context.level }
// 子组件 class Son extends Component { render() { console.log(“son”, this.context); return (
孙子组件
{ this.context.nickname }
{ this.context.level }
// 重孙组件 class Child extends Component { constructor(props) { super() } render() { console.log(“child”, this.context); return (
child组件
姓名:{ this.context.nickname }
等级:{ this.context.level }
使用原理:
1. 使用React.createContext({})来创建一个上下文对象,向函数中传入一个配置对象,如果我们的传值的根组件没有包裹需要使用context上下文的组件,那么子组件使用的就是我们最开始创建的context上下文对象传入的默认值(对象)
2. 使用创建的上下文对象来映射为一个组件标签 value为我们传递的值 将我们传递的值 置于组件标签里面ContextName => <ContextName.Provider value={ this.props }> <Root /> <ContextName.Provider>
3. 组件在使用的时候,先创建声明组件,然后再声明组件的上下文对象
例如Son组件: Son.contextType = ContrextName
<a name="x6B1B"></a>
###
<a name="zucVi"></a>
## 2.3 函数式组件如何消费context
在函数式组件中,组件的内部是没有this可以使用的,函数式组件就是无状态组件,因为在最开始的时候就订阅了context的内容,那么我们可以在组件中进行相应的消费使用<ContextName.Consumer></ContextName.Consumer>里面传入一个回调函数 回调函数的参数就是传入的context的内容,在消费组件的内部就可以直接将jsx的内容进行返回,还可以直接使用订阅的上下文对象
```javascript
// 连上面的案例 修改子组件
function Child() {
return (
<UserContext.Consumer>
{
value => {
return (
<div>
<p>child组件111</p>
<h2>姓名:{ value.nickname }</h2>
<h2>等级:{ value.level }</h2>
</div>
)
}
}
</UserContext.Consumer>
)
}
2.4 创建多个上下文对象,多次消费
// 创建多个上下文对象 可以进行嵌套消费
import React, { Component } from 'react'
// 创建context对象 参数是默认的初始化对象
// context组件命名的时候 最好是大写的形式 因为最后会被映射为组件标签
const UserContext = React.createContext({
nickname: "默认的姓名",
level: 123456
})
const ThemeContext = React.createContext({
color: 'pink'
})
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
nickname: "weiwei",
level: 18
}
}
render() {
return (
<div className="root">
<p>根组件的状态数据</p>
<h2>{ this.state.nickname }</h2>
<h2>{ this.state.level }</h2>
<button onClick={() => this.btnClick() }>修改根组件的状态</button>
{/* 需要传递数据的根组U件 将我们需要传递数据的组件 置于该组件中
需要传递的数据当作value属性的属性值 */}
<UserContext.Provider value={ this.state }>
{/* 需要传递的根组件 */}
<ThemeContext.Provider value={{ color: 'pink' }}>
<Parent />
</ThemeContext.Provider>
</UserContext.Provider>
</div>
)
}
btnClick() {
this.setState({
nickname: this.state.nickname + 1,
level: this.state.level + 1
})
}
}
// 父组件
class Parent extends Component {
render() {
console.log("parent", this.context);
return (
<div className="parent">
<p>父组件</p>
<h2>{ this.context.nickname }</h2>
<h2>{ this.context.level }</h2>
<Son />
</div>
)
}
}
// 订阅context数据
Parent.contextType = UserContext
// 子组件
class Son extends Component {
render() {
console.log("son", this.context);
return (
<div className="son">
<p>孙子组件</p>
<h2>{ this.context.nickname }</h2>
<h2>{ this.context.level }</h2>
<hr/>
<Child />
</div>
)
}
}
// 子组件需要订阅传递的数据
Son.contextType = UserContext
// 孙子组件 可以直接使用context来订阅数据
// class Child extends Component {
// constructor(props) {
// super()
// }
// render() {
// console.log("child", this.context);
// return (
// <div>
// <p>child组件</p>
// <h2>姓名:{ this.context.nickname }</h2>
// <h2>等级:{ this.context.level }</h2>
// </div>
// )
// }
// }
// Child.contextType = UserContext
// 将上面的组件 改造为函数式组件
function Child() {
return (
<UserContext.Consumer>
{
value => {
return (
<ThemeContext.Consumer>
{
theme => {
return (
<div>
<p>child组件111</p>
<h2>姓名:{ value.nickname }</h2>
<h2>等级:{ value.level }</h2>
<h2 style={{ backgroundColor: theme.color }}>主题颜色:{ theme.colot }</h2>
</div>
)
}
}
</ThemeContext.Consumer>
)
}
}
</UserContext.Consumer>
)
}
2.4.1 先创建多个上下文对象
// 创建context对象 参数是默认的初始化对象
// context组件命名的时候 最好是大写的形式 因为最后会被映射为组件标签
const UserContext = React.createContext({
nickname: "默认的姓名",
level: 123456
})
const ThemeContext = React.createContext({
color: 'pink'
})
2.4.2 订阅上下文对象
// 上下文对象的订阅是可以进行嵌套的
render() {
return (
<div className="root">
<p>根组件的状态数据</p>
<h2>{ this.state.nickname }</h2>
<h2>{ this.state.level }</h2>
<button onClick={() => this.btnClick() }>修改根组件的状态</button>
{/* 需要传递数据的根组U件 将我们需要传递数据的组件 置于该组件中
需要传递的数据当作value属性的属性值 */}
<UserContext.Provider value={ this.state }>
{/* 需要传递的根组件 */}
<ThemeContext.Provider value={{ color: 'pink' }}>
<Parent />
</ThemeContext.Provider>
</UserContext.Provider>
</div>
)
}
2.4.3 函数式组件进行嵌套消费
// 将上面的组件 改造为函数式组件
function Child() {
return (
<UserContext.Consumer>
{
value => {
return (
<ThemeContext.Consumer>
{
theme => {
return (
<div>
<p>child组件111</p>
<h2>姓名:{ value.nickname }</h2>
<h2>等级:{ value.level }</h2>
<h2 style={{ backgroundColor: theme.color }}>主题颜色:{ theme.colot }</h2>
</div>
)
}
}
</ThemeContext.Consumer>
)
}
}
</UserContext.Consumer>
)
}