0、认识组件间的通信
在开发过程中,我们会经常遇到需要组件之间相互进行通信:
- 比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让其进行展示;
- 又比如我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给他们来进行展示;
- 也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;
总之,在一个React项目中,组件之间的通信是非常重要的环节; 父组件在展示子组件,可能会传递一些数据给子组件:
-
父组件通过 属性=值 的形式来传递给子组件数据;
-
子组件通过 props 参数获取父组件传递过来的数据;
一、父传子通信——类组件
二、父传子通信——函数式组件
三、属性验证——参数propTypes
对于传递给子组件的数据,有时候我们可能希望进行验证,特别是对于大型项目来说:
- 当然,如果你项目中默认继承了Flow或者TypeScript,那么直接就可以进行类型验证;
- 但是,即使我们没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证;
- 从 React v15.5 开始,React.PropTypes 已移入另一个包中:prop-types 库
更多的验证方式,可以参考官网:https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html
- 比如验证数组,并且数组中包含哪些元素;
- 比如验证对象,并且对象中包含哪些key以及value是什么类型;
- 比如某个原生是必须的,使用 requiredFunc: PropTypes.func.isRequired
如果没有传递,我们希望有默认值呢?
- 我们使用defaultProps就可以了
四、子组件传递父组件
某些情况,我们也需要子组件向父组件传递消息:
- 在vue中是通过自定义事件来完成的;
- 在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;
将计数器案例进行拆解;
- 将按钮封装到子组件中:CounterButton;
- CounterButton发生点击事件,将内容传递到父组件中,修改counter的值;
import React, {
PureComponent} from 'react';
class CounterButton extends PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<button onClick={
e => this.counterIncrement()}>CounterButton里的+1</button>
)
}
counterIncrement() {
const {
increment} = this.props
increment()
// this.props.increment()
}
}
class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (
<div>
<h2>当前计数: {
this.state.counter}</h2>
<button onClick={
e => this.increment()}>App里的+</button>
<br/>
<CounterButton increment={
e => this.increment()} />
</div>
);
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
export default App ;
import React, {
Component } from 'react';
function ProfileHeader (props) {
return (
<div>
<h2>用户昵称: {
props.nickname}</h2>
<h2>用户等级: {
props.level}</h2>
</div>
)
}
function Profile (props) {
return (
<div>
{
/* <ProfileHeader nickname={props.nickname} level={props.level} /> */}
<ProfileHeader {
...props} />
<ul>
<li>设置1</li>
<li>设置2</li>
<li>设置3</li>
<li>设置4</li>
</ul>
</div>
)
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
nickname: 'zepp',
level: 99
}
}
render() {
const {
nickname, level} = this.state
return (
<div>
app
<Profile nickname={
nickname} level={
level} />
</div>
);
}
}
export default App;
五、Context应用场景
非父子组件数据的共享:
- 在开发中,比较常见的数据传递方式是通过props属性自上而下(由父到子)进行传递。
- 但是对于有一些场景:比如一些数据需要在多个组件中进行共享(地区偏好、UI主题、用户登录状态、用户信息等)。
- 如果我们在顶层的App中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的操作。
但是,如果层级更多的话,一层层传递是非常麻烦,并且代码是非常冗余的:
- React提供了一个API:Context;
- Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props;
- Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;
六、Context相关API
1. React.createContext
- 创建一个需要共享的Context对象:
- 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值;
- defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
2. Context.Provider
- 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:
- Provider 接收一个 value 属性,传递给消费组件;
- 一个 Provider 可以和多个消费组件有对应关系;
- 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
- 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染;
3. Class.contextType
- 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
- 这能让你使用 this.context 来消费最近 Context 上的那个值;
- 你可以在任何生命周期中访问到它,包括 render 函数中;
4. Context.Consumer
- 这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context。
- 这里需要 函数作为子元素(function as child)这种做法;
- 这个函数接收当前的 context 值,返回一个 React 节点;
什么时候使用默认值defaultValue呢?
什么时候使用Context.Consumer呢?
- 1.当使用value的组件是一个函数式组件时;
- 2.当组件中需要使用多个Context时;
import React, {
Component} from 'react';
// 创建Context对象
const UserContext = React.createContext({
nickname: 'bbb',
level: -1
})
/* function ProfileHeader (props) {
return (
<div>
<h2>用户昵称: </h2>
<h2>用户等级: </h2>
</div>
)
} */
class ProfileHeader extends Component {
render() {
console.log('====================================');
console.log(this.context);
console.log('====================================');
return (
<div>
<h2>用户昵称:{
this.context.nickname} </h2>
<h2>用户等级:{
this.context.level} </h2>
</div>
)
}
}
ProfileHeader.contextType = UserContext
function Profile(props) {
return (
<div>
{
/* <ProfileHeader nickname={props.nickname} level={props.level} /> */}
<ProfileHeader/>
<ul>
<li>设置1</li>
<li>设置2</li>
<li>设置3</li>
<li>设置4</li>
</ul>
</div>
)
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
nickname: 'zepp222',
level: 99
}
}
render() {
const {
nickname, level} = this.state
return (
<div>
app
<UserContext.Provider value={
this.state}>
<Profile/>
</UserContext.Provider>
</div>
);
}
}
export default App;
import React, {
Component} from "react";
// 创建Context对象
const UserContext = React.createContext({
nickname: "bbb",
level: -1,
});
function ProfileHeader(props) {
return (
<UserContext.Consumer>
{
(value) => {
return (
<div>
<h2>用户昵称:{
value.nickname} </h2>
<h2>用户等级:{
value.level} </h2>
</div>
);
}}
</UserContext.Consumer>
);
}
/* class ProfileHeader extends Component {
render() {
console.log('====================================');
console.log(this.context);
console.log('====================================');
return (
<div>
<h2>用户昵称:{this.context.nickname} </h2>
<h2>用户等级:{this.context.level} </h2>
</div>
)
}
} */
// ProfileHeader.contextType = UserContext;
function Profile(props) {
return (
<div>
<ProfileHeader/>
<ul>
<li>设置1</li>
<li>设置2</li>
<li>设置3</li>
<li>设置4</li>
</ul>
</div>
);
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
nickname: "zepp",
level: 99,
};
}
render() {
return (
<div>
app
<UserContext.Provider value={
this.state}>
<Profile/>
</UserContext.Provider>
</div>
);
}
}
export default App;
多个context嵌套:
import React, {
Component } from "react";
// 创建Context对象
const UserContext = React.createContext({
nickname: "bbb",
level: -1,
});
const ThemeContext = React.createContext({
color: "black",
});
function ProfileHeader(props) {
return (
<UserContext.Consumer>
{
(value) => {
return (
<ThemeContext.Consumer>
{
(theme) => {
return (
<div>
<h2>用户昵称:{
value.nickname} </h2>
<h2>用户等级:{
value.level} </h2>
<h2>颜色: {
theme.color}</h2>
</div>
);
}}
</ThemeContext.Consumer>
);
}}
</UserContext.Consumer>
);
}
/* class ProfileHeader extends Component {
render() {
console.log('====================================');
console.log(this.context);
console.log('====================================');
return (
<div>
<h2>用户昵称:{this.context.nickname} </h2>
<h2>用户等级:{this.context.level} </h2>
</div>
)
}
} */
ProfileHeader.contextType = UserContext;
function Profile(props) {
return (
<div>
<ProfileHeader />
<ul>
<li>设置1</li>
<li>设置2</li>
<li>设置3</li>
<li>设置4</li>
</ul>
</div>
);
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
nickname: "zepp",
level: 99,
};
}
render() {
return (
<div>
app
<UserContext.Provider value={
this.state}>
<ThemeContext.Provider value={
{
color: "red" }}>
<Profile />
</ThemeContext.Provider>
</UserContext.Provider>
</div>
);
}
}
export default App;