REACT 基础-组件通讯-类组件
1.组件通讯概述
目标: 掌握什么事组件通讯
对于不同的组件而言,组件通讯事指数据能够在不同的组件之间进行流动。
在使用组件构建用户界面时,我们会将一个完整的页面拆分为若干个独立的小组件,这些组件在完成不同任务时,通常需要协同工作,比如多个组件需要用到相同的数据,因此组件之间必须能够相互通信,即在不同的组件之间传递数据。
组件通讯事包含了: 父子通讯,兄弟通讯,远房亲戚通讯。
对于同一个组件而言,组件通讯可以实现差异化复用组件。
2.组件通讯的基础
目标:掌握在组件外部向组件内部传递数据的方式,掌握在组件内部获取外部传递进来的数据方式
在调用组件时通过属性的方式向组件内部传递数据。
//src/App.js
import React from "react";
import Person from "./Person"
export default class App extends React.Component {
render() {
return <Person name="张三" age="20"></Person>
}
}
在类组件内部通过this.props获取外部传递进来的数据,this.props为对象类型,外面传递进来的数据都保留在该对象中
比如在调用Person组件时传递了name属性和age属性,那么在this.props对象中就会存在name属性和age属性
//src/Person.js
import React from "react";
export default class Person extends React.Component {
render() {
return <>{this.props.name} {this.props.age} </>
}
}
// 问题: 为什么当前类中没有显式定义 props, 我们仍然可以通过 this 来获取 props 呢?
// 以上代码是简写形式, 完整的写法如下:
// React 内部在实例化 Person 类时, 通过参数的方式接收了外部数据
// 所以我们在类的内部可以通过构造函数参数接收外部数据
// 由于 Person 类继承了 Component 类, 所以在构造函数中需要调用 super 方法, 这是 JavaScript 语法要求
// React 通过 super 方法的参数, 将 props 传递到了父类 Component 中
// 在父类 Component 中, 有这么一句代码, this.props = props, 也就是说父类有 props 属性, 值为外部传递进来的数据
// 所以在子类中我们就可以通过 this 来获取 props 了
export default class Person extends React.Component {
constructor(props) {
super(props);
}
render() {
return <>{this.props.name} {this.props.age}</>;
}
}
3.组件通讯注意事项
目标:掌握组件通讯的两个注意事项
(1) 在为组件传递数据时数据类型可以是任意的。
字符串、数值、布尔、数组、对象、函数、JSX
//src/App.js
export default class App extends React.Component {
render() {
return (
<Person
name="张三"
age={20}
isMarry={false}
skills={["编程","设计师"]}
style={{color:"red"}}
sayHi={()=> alert("Hi")}
jsx={<em>我是em标记</em>}
></Person>
)
}
}
//src/Person.js
export default class Person extends React.Component {
render() {
return (
<>
<p>{this.props.name}</p>
<p>{this.props.age}</p>
<p>{this.props.isMarry ? "已婚" : "未婚"}</p>
<p>{this.props.skills.map((skill)=> <span key={skill}>{skill}</span>)}</p>
<p style={this.props.style}>我是红色的文字</p>
<p onClick={this.props.sayHi}>打个招呼</p>
<p>{this.props.jsx}</p>
</>
)
}
}
import React from "react";
export default class App extends React.Component {
state= {
person: {
name:"张三",
age: 20,
isMarry: false,
}
}
render() {
return <Person {...this.state.person} />;
}
}
class Person extends React.Component {
render() {
console.log(this.props); // {name: "张三", age: 20, isMarry: false}
return <></>;
}
}
单向数据流使的应用程序中的数据流向变得清晰,使应用程序更加可控、更加容易调试。
如果所有子组件都可以任意修改 props,那么当程序出现问题之后,问题的定位将变得异常复杂。
React 为了让开发者遵循单向数据流原则,将 props 设置为了只读的,下层组件在拿到 props 以后不能修改,避免下层组件修改 props 破坏单向数据流原则。
// src/Person.js
export default class Person extends React.Component {
render() {
this.props.name = "李四";
}
}
4.父传子组件通讯
目标:掌握父组件如何向子组件传递数据
//src/Parent.js
import React from "react"
import Child from "./Child"
export default class Parent extends React.Component {
state = {
name:"张三",
age:20
};
render() {
return (
<div>
我是父组件
<Child name={this.state.name} age={this.state.age}></Child>
</div>
)
}
}
//src/Child.js
import React from "react";
export default class Child extends React.Component {
render() {
const {name,age} = this.props;
return <div>我是子组件 {name}-{age}</div>
}
}
练习:父组件向子组件传递数据
import React from "react";
import "./styles.css";
export default class App extends React.Component {
state = {
list:[
{
id: 1,
name: "超级好吃的棒棒糖",
price: 18.8,
info: "开业大酬宾,全场8折",
},
{
id: 2,
name: "超级好吃的大鸡腿",
price: 34.2,
info: "开业大酬宾,全场8折",
},
{
id: 3,
name: "超级无敌的冰激凌",
price: 14.2,
info: "开业大酬宾,全场8折",
},
]
}
render() {
return (
{/* 将以下内容抽取成独立的子组件 */}
<div className="parent">
<div className="child">
<div className="product">
<h3>标题:超级好吃的棒棒糖</h3>
<div>价格:18.8</div>
<p>开业大酬宾,全场8折</p>
<button>砍价</button>
</div>
</div>
</div>
)
}
}
/* 基础样式 */
.parent {
width: 80%;
padding: 10px;
border: 2px solid black;
}
.child {
margin: 10px 0;
padding: 10px;
}
.product {
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
//父组件
export default class App extends React.Component {
render() {
return (
<div className="parent">
{this.state.list.map((item)=>(
<Child key={item.id} item={item}></Child>
))}
</div>
)
}
}
//子组件
class Child extends React.Component {
render() {
return (
<div className="child">
<div className="product">
<h3>标题:{this.props.item.name}</h3>
<div>价格:{this.props.item.price}</div>
<p>{this.props.item.price}</p>
<button>砍价</button>
</div>
</div>
)
}
}
5.子传父组件通讯
目标:掌握子组件更改父组件状态的方式
子组件默认情况下没有权限修改传父组件的状态,但是父组件本身可以更改自己的状态。
我们可以在父组件中声明一个用于修改状态的方法,这个方法接受新状态作为参数,然后将该方法传递给子组件。
子组件通过调用父组件传递过来的修改状态的方法对父组件中的状态进行修改
//父组件
import React from "react";
export default class App extends React.Component {
constructor() {
super();
this.modifyMsg = this.modifyMsg.bind(this)
}
state = {
msg:"Hello React"
}
modifyMsg(msg) {
this.setState({
msg,
})
}
render() {
return (
<>
<div>父组件:{this.state.msg}</div>
<Message msg={this.state.msg} modifyMsg={this.modifyMsg />
</>
)
}
}
class Message extends React.Component {
render() {
return (
<div>
子组件:{this.props.msg}
<button onClick={()=> this.props.modifyMsg("Hi, I am from Child Component")}>button</button>
</div>
)
}
}
练习:子组件向父组件传递数据
npm install number-precision
import NP from "number-precision";
export default class App extends React.Component {
constructor(props) {
super(props)
this.bargin = this.bargin.bind(this)
}
//砍价
bargin(id,price) {
const newList = this.state.list.map((item)=>{
if(item.id === id) {
return {
...item,
price:NP.minus(item.price,price)
}
}else {
return item;
}
})
this.setState({
list:newList,
})
}
render() {
return (
<div className="parent">
<h1>父组件</h1>
{this.state.list.map((item)=>(
<Child bargin={this.bargin} />
))}
</div>
)
}
}
//子组件
class Child extends React.Component {
render() {
return <button onClick={()=>this.props.bargin(this.props.id,1)}>砍价</button>
}
}
6.兄弟组件通讯
目标: 通过状态提升思想完成兄弟组件数据通讯
组件状态提升指的是将兄弟组件之间的共享状态提升到最近的公共父级组件中,由公共父级组件维护这个状态和修改状态的方法,父级组件再通过props的方式将状态数据传递到两个子组件中。
import React from "react";
//父组件
export default class App extends React.Component {
constrouctor(props) {
super(props)
this.modifyMsg = this.modifyMsg.bind(this)
}
state = {
msg: "Hello React"
}
modifyMsg(msg) {
this.setState({msg})
}
render() {
return (
<>
<Message msg={this.state.msg}></Message>
<Machine modifyMsg={this.state.modifyMsg}></Machine>
</>
)
}
}
- 子组件Message
class Message extends React.Component {
render() {
return <div>{this.props.msg}</div>
}
}
- 子组件Machine
class Machine extends React.Component {
render() {
return (
<buttton onClick={()=>this.props.modifyMsg("Hi,React.js")}>button</buttton>
)
}
}
7.跨级组件通讯
目标: 掌握使用context 进行组件通讯的方式
为了让兄弟组件实现状态共享我们通常会将状态进行提升,提升到它们最近的公共父级,
但是如果当组件层级关系比较复杂和深的时候这种方式其实不是非常理想,因为在这个过程中有许多组件并不需要使用该状态但是却参与了状态的传递,我们将这种情况称之为prop drilling.
为了解决以上问题 React 提供了 Context(上下文),它允许跨组件层级传递状态,只要该组件在Context的范围内都可以直接获取状态。
//src/index.js
import react,{createContext,Component} from "react";
export const {Provider,Consumer} = createContext({name:"里斯本",age:50});
root.render(
<Provider value={{name:"张三",age:20}}>
<App />
</Provider>
)
//src/App.js
import React from "react";
import Middle from "./Middle";
export default class App extends React.Component {
render() {
return <Middle />
}
}
//src/Middle.js
import React from 'react';
import Message from "./Message";
export default class Middle extends React.Component {
render() {
return <Message />
}
}
//src/Message.js
import React from "react";
import {Consumer} from "./index"
export default class Message extends React.Component {
render() {
return (
<Consumer>
{(value)=> <div>{value.name q}{value.age}</div>}
</Consumer>
)
}
}
8.综合案例-评论
效果图
9.组件子节点
目标:掌握组件子节点的用法
- 学习组件子节点的基本用法
- 学习操作组件子节点的属性和方法
- 学习组件子节点的基本用法
在调用组件时可以为组件标签添加子节点,通过子节点也可以向组件内部传递数据
export default class App extends React.Component {
render() {
return <Message>Hello Message</Message>
}
}
class Message extends React.COmponent {
render() {
return <>{this.props.children}</>
}
}
//等价写法
export default class App extends React.Component {
render() {
return <Message children="Hello Message">Hello Message</Message>
}
}
标签子节点的类型可以是字符串,数值,数组,函数,JSX等等
<Message>123</Message>
<Message>
<p>Hello Message</p>
</Message>
<Message>
<p>Hello Message</p>
<p>Hello World</p>
</Message>
export default class App extends React.Component {
render() {
return (
<Message>
<Machine />
</Message>
)
}
}
class Machine extends React.Component {
render() {
return <div>Machine</div>
}
}
class Message extends React.Component {
render() {
return <div>{this.props.children}</div>
}
}
<Message>{["a","b","c"]}</Message>
export default class App extends React.Component {
render() {
return (
<Message>
{()=> {console.log("hello React")}}
</Message>
)
}
}
class Message extends React.Component {
render() {
return <div onClick={this.props.children}></div>
}
}
export default class App extends React.Component {
render() {
return <Message>{()=> <p>Hello World</p>}</Message>
}
}
class Message extends React.Component {
render() {
return <div>{this.props.children()}</div>
}
}
export default class App extends React.Componet {
render() {
return <Message>{(text)=> <p>{text}</p>}</Message>
}
}
class Message extends React.Component {
render() {
return <div>{this.props.children("hi react")}</div>
}
}
export default class App extends React.Component {
render() {
return <Message>{(jsx)=> <p>{jsx}</p>}</Message>
}
}
class Message extends React.Component {
render() {
return <div>{this.props.children(<span>我是span</span>)}</div>
}
}
练习:使用组件子节点增强组件的复用能力
//根组件
export default class App extends React.Component {
render() {
return <HomePage>我是右侧区域内容</HomePage>
}
}
//头部组件
class Header extends React.Component {
render() {
return <div>Header</div>
}
}
//侧边栏组件
class SideBar extends React.Component {
render() {
return <div>SideBar</div>
}
}
//用于复用头部组件和侧边栏组件的组件
class Multiplex extends React.Component {
render() {
return (
<>
<Header />
<SideBar />
<div>{this.props.children}</div>
</>
)
}
}
//首页组件
class HomePage extends React.Component {
render() {
return <Mutiplex>我是列表右侧区域内容</Mutiplex>
}
}
//列表页组件
class ListPage extends React.Component {
render() {
return <Multiplex>我是列表右侧区域内容</Multiplex>
}
}
- 学习操作组件子节点的属性和方法
React.Children.only: 限制组件子节点只能有一个子节点且必须有
export default class App extends React.Component {
render(){
return (
<Message>
<p>Hello</p>
</Message>
)
}
}
class Message extends React.Component {
render() {
try {
// 检测组件的子节点是否只有一个
React.Children.only(this.props.children)
}catch(error) {
return <div>Message 组件的子节点有且只能有一个</div>
}
return <div>{this.props.children}</div>
}
}
React.Children.count:获取子节点的数量
class Message extends React.Component {
render() {
return <>{React.Children.count(this.props.children}</>
}
}
React.children.map: 对组件子节点进行转换
import React from "react"
export default class App extends React.Component {
render() {
return (
<Message>
<img src="https://images.pexels.com/photos/10198426/pexels-photo-10198426.jpeg"
alt=""
width="300px" />
<img src="https://images.pexels.com/photos/4386364/pexels-photo-4386364.jpeg"
alt=""
width="300px" />
</Message>
)
}
}
class Message extends React.Compont {
render() {
return (
{React.Chilren.map(this.props.children,(item)=> <a href="https://www.baidu.com">{item}</a>)}
)
}
}
React.Children.toArray:将组件子节点转换为数组
如果组件有多个子节点,this.props.children是数组类型,如果组件只有一个子节点,
this.props.chidlren是对象类型,如果组件没有子节点,this.props.children 为undefined类型。通过React.children.toArray 方法保证this.props.children为一直为数组类型,以保证Children.map方法可用。
class Message extends React.Component {
render() {
console.log(React.children.toArray(this.props.children));
}
}
练习: 封装图片切换组件。
import React from "react";
export default class App extends React.Component {
render() {
return (
<ImageToggle>
<img
src="https://images.pexels.com/photos/10198426/pexels-photo-10198426.jpeg"
alt=""
width="300px"
/>
<img
src="https://images.pexels.com/photos/4386364/pexels-photo-4386364.jpeg"
alt=""
width="300px"
/>
<img
src="https://images.pexels.com/photos/9812128/pexels-photo-9812128.jpeg"
alt=""
width="300px"
/>
<img
src="https://images.pexels.com/photos/8746965/pexels-photo-8746965.jpeg"
width="300px"
alt=""
/>
</ImageToggle>
);
}
}
class ImageToggle extends React.Component {
constructor(props) {
super(props);
this.state = {
index: 0,
total: React.Children.count(props.children),
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler() {
this.setState({
index:
this.state.index + 1 > this.state.total - 1 ? 0 : this.state.index + 1,
});
}
render() {
return (
<>
{
React.Children.map(this.props.children, (item) => (
<div className="imgContainer" onClick={this.onClickHandler}>
{item}
</div>
))[this.state.index]
}
</>
);
}
}