React组件化开发(6)- 组件之间的通信(3)
6.1父子组件通信
父组件传数据给子组件:
- 通过 props 属性自上而下(由父及子)进行传递
- 父组件通过 属性=值 的形式来传递数据给子组件
- 子组件通过 props 对象参数获取父组件传过来的数据
1.类组件写法
import React, { Component } from 'react'
class ChildCpn extends Component {
constructor(props) {
super(props);
// 父组件会把数据传到props这个对象中
this.Info = this.props;
console.log(this.props);
}
render() {
console.log(this.Info);
return (
<div>
<h2>我是ChildCpn子组件</h2>
<h2>{"我的名字叫" + this.Info.name + ",今年" + this.Info.age + "岁,身高是:" + this.Info.width}</h2>
</div>
)
}
}
export default class App extends Component {
render() {
return (
<div>
<ChildCpn name="权爷" age="17" width="1.88" />
</div>
)
}
}
2.函数组件写法
函数组件的写法更简单一些
import React from 'react'
function ChildCpn(props) {
const { name, age, width } = props;
return (
<div>
<h2>我是ChildCpn函数子组件</h2>
<h2>{name + "," + age + "," + width}</h2>
</div>
)
}
export default function App() {
return (
<div>
<ChildCpn name="curry" age="33" width="1.91" />
<ChildCpn age="27" width="2.01" />
</div>
)
}
6.2对组件传过来的值进行类型校验
例如上面的name、age、width都是以字符串的类型传过去的
如果希望它们的类型是指定的值该怎么做?
- 如果你项目中默认继承了Flow或者TypeScript,那么直接就可以进行类型验证
- 即使没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证
- 从 React v15.5 开始,React.PropTypes 已移入另一个包中:prop-types 库
- 函数/类组件名.propTypes = {}
import React from 'react'
import PropTypes from 'prop-types';
function ChildCpn(props) {
const { name, age, width, } = props;
return (
<div>
<h2>我是ChildCpn函数子组件</h2>
<h2>{name + "," + age + "," + width}</h2>
<ul>
{
list.map((item, index) => {
return <li>{item}</li>
})
}
</ul>
</div >
)
}
// 设置传递值的类型
// 注意这里的propTypes 首字母是小写的
ChildCpn.propTypes = {
name: PropTypes.string,
age: PropTypes.number,
width: PropTypes.number,
list: PropTypes.array
}
export default function App() {
return (
<div>
<ChildCpn name="curry" age={33} width={1.91} list={["nba", "cba", "aba"]} />
<ChildCpn name="威金斯" age={27} width={2.01} list={["aaa", "bbb", "ccc"]} />
</div>
)
}
6.3设置组件传过来的值的默认值
- 函数/类组件名.defaultProps= {}
import React from 'react'
// import PropTypes from "prop-types"
import PropTypes from 'prop-types';
function ChildCpn(props) {
const { name, age, width, } = props;
const { list } = props;
return (
<div>
<h2>我是ChildCpn函数子组件</h2>
<h2>{name + "," + age + "," + width}</h2>
<ul>
{
list.map((item, index) => {
return <li>{item}</li>
})
}
</ul>
</div >
)
}
// 设置传递值的类型
// 注意这里的propTypes 首字母是小写的
ChildCpn.propTypes = {
// isRequired 表示必须要传的值
name: PropTypes.string.isRequired,
age: PropTypes.number,
width: PropTypes.number,
list: PropTypes.array
}
// 设置默认值
ChildCpn.defaultProps = {
name: '权爷',
age: 17,
width: 1.88,
list: ['你好', '我是', 'xxx']
}
export default function App() {
return (
<div>
<ChildCpn name="curry" age={33} width={1.91} list={["nba", "cba", "aba"]} />
<ChildCpn name="威金斯" age={27} width={2.01} list={["aaa", "bbb", "ccc"]} />
<ChildCpn />
</div>
)
}
6.4类组件另外一种方式设置类型检验和默认值
import React, { Component } from 'react'
import PropTypes from 'prop-types';
// 如果是类组件它的默认值有另外一种写法:
class ChildCpn2 extends Component {
// es6中的class fields写法
static defaultProps = {
name: "类组件的默认值写法",
age: "类组件的默认值写法",
width: "类组件的默认值写法"
}
static propTypes = {
// xxx
}
constructor(props) {
super(props);
this.props = this.props;
}
render() {
return (
<div>
<h2>{this.props.name + "," + this.props.age + "," + this.props.width}</h2>
</div>
)
}
}
export default function App() {
return (
<div>
<ChildCpn2 name="哈哈" age="哈哈" width="哈哈" />
<ChildCpn2 />
</div>
)
}
6.5子父组件通信
子组件向父组件传递数据,同样是通过props传递消息,只是在父组件传递一个回调函数,在子组件调用这个函数即可。
import React, { Component, createRef } from 'react'
class BtnIncrment extends Component {
render() {
return (
<div>
<button onClick={this.props.incrment}>+1</button>
</div>
)
}
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
const { counter } = this.state;
return (
<div>
<h2>当前计数:{counter}</h2>
<button onClick={this.btnClick} >+1</button>
<BtnIncrment incrment={this.btnClick} />
</div>
)
}
btnClick = () => {
{
this.setState({
counter: this.state.counter + 1
})
}
}
}
6.6组件通信案例
效果:
App.js
import React, { Component } from 'react'
import "./index.css"
import TabContainer from "./TabContainer"
export default class App extends Component {
constructor() {
super();
this.titles = ["流行", "新款", "精选"]
this.state = {
currentTitle: 0
}
}
render() {
const { currentTitle } = this.state
return (
<div>
<TabContainer
titles={this.titles}
getIndex={index => this.getIndex(index)} />
<h2>{this.titles[currentTitle]}</h2>
</div>
)
}
getIndex(index) {
this.setState({
currentTitle: index
})
}
}
TabContainer.js
import { Component } from "react"
export default class TabContainer extends Component {
constructor(props) {
super(props);
this.state = {
currentIndex: 0
}
}
render() {
const { titles } = this.props;
return (
<div className="tab_container" >
{
titles.map((item, index) => {
return (
<div className={"item " + ((this.state.currentIndex == index) ? "active" : "")}
onClick={() => { this.itemClick(index) }}>
{item}
<span></span>
</div>
)
})
}
</div >
)
}
itemClick(index) {
this.setState({
currentIndex: index
})
this.props.getIndex(index)
}
}
index.css
.tab_container {
display: flex;
height: 44px;
line-height: 44px;
}
.tab_container .item {
flex: 1;
text-align: center;
}
.tab_container .item.active {
color: red;
}
.tab_container .item.active span {
display: block;
width: 50%;
margin-left: 50%;
transform: translateX(-50%);
border-bottom: 3px solid red;
}
最终webpage打包入口是index.js,打包import的文件
7.react实现slot
在vue中有插槽这个概念,而react中是没有的,因为react压根就不需要,jsx可以写变量,数据。
7.1组件标签内传内容
如果把内容放在组件标签里面,它会传入到这个组件的props.children里面,并且这个children是个数组。
App.js
import React, { Component } from 'react'
import "./App.css"
function TabContainer(props) {
console.log(props);
return (
<div className="Container">
<div className="leftSlot">{props.children[0]}</div>
<div className="centerSlot">{props.children[1]}</div>
<div className="rightSlot">{props.children[2]}</div>
</div>
)
}
export default class App extends Component {
render() {
return (
<div>
<TabContainer>
<span>左边</span>
<span>中间</span>
<span>右边</span>
</TabContainer>
</div>
)
}
}
App.css
body {
margin: 0;
padding: 0;
}
.Container {
display: flex;
text-align: center;
height: 44px;
line-height: 44px;
}
.leftSlot,
.rightSlot {
width: 80px;
}
.leftSlot {
background-color: green;
}
.rightSlot {
background-color: blue;
}
.centerSlot {
flex: 1;
background-color: pink;
}
这种不能改变顺序。
7.2父传子通信的方式
这种就不必在意它的顺序,能精准定位到某个元素里。
import React, { Component } from 'react'
import "./App.css"
function TabContainer(props) {
console.log(props);
const { leftSlot, centerSlot, rightSlot } = props;
return (
<div className="Container">
<div className="leftSlot">{leftSlot}</div>
<div className="centerSlot">{centerSlot}</div>
<div className="rightSlot">{rightSlot}</div>
</div>
)
}
export default class App extends Component {
render() {
return (
<div>
<TabContainer
leftSlot={<span>左边的插槽</span>}
centerSlot={<span>中间的插槽</span>}
rightSlot={<span><a href="blog-qh.com">中间的插槽</a></span>} />
</div>
)
}
}
8.跨级组件通信
有一种办法,就是逐层props传递下去,但是中间层用不到这个传过来的数据,代码比较冗余。比较麻烦。
8.1逐层传递
属性展开(JSX独有写法):如果你已经有了一个 props 对象,你可以使用展开运算符 ...
来在 JSX 中传递整个 props 对象。
import React, { Component } from 'react'
function ProFileHeader(props) {
console.log(props);
return (
<div>
<h2>您的昵称:{props.nickName}</h2>
<h2>等级:{props.grade}</h2>
</div>
)
}
class ProFile extends Component {
render() {
// console.log(this.props);
return (
<div>
{/* <ProFileHeader nickName={this.props.nickName} grade={this.props.grade} /> */}
<ProFileHeader {...this.props} />
</div>
)
}
}
export default class App extends Component {
constructor() {
super();
this.state = {
nickName: "权爷",
grade: 100
}
}
render() {
return (
<div>
{/* <ProFile nickName={this.staet.nickName} grade={this.staet.grade} /> */}
{/* 属性展开,JSX的独有写法,展开传入 */}
<ProFile {...this.state} />
</div>
)
}
}
8.2Context
React提供了一个API:Context;
Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props;
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;
每个类组件里都有一个context
1.Context相关API
React.createContext
- 创建一个需要共享的Context对象
- 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前context值;
- React.createContext的参数是默认值,组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
Context.Provider
- 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:
- Provider 接收一个 value 属性,传递给消费组件
- 一个 Provider 可以和多个消费组件有对应关系
- 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据
- 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染
Class.contextType
- 只有类组件有这个属性
- 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
- 这能让你使用 this.context 来消费最近 Context 上的那个值;
import React, { Component } from 'react'
const UserInfo = React.createContext({
nickName: '匿名',
grade: -1
})
class ProFileHeader extends Component {
render() {
console.log(this.context);
return (
<div>
<h2>您的昵称:{this.context.nickName}</h2>
<h2>等级:{this.context.grade}</h2>
</div>
)
}
}
ProFileHeader.contextType = UserInfo
class ProFile extends Component {
render() {
console.log(this.context);
console.log(this.contextType);
return (
<div>
<ProFileHeader />
</div>
)
}
}
export default class App extends Component {
constructor() {
super();
this.state = {
nickName: "权爷",
grade: 100
}
}
render() {
return (
<div>
<UserInfo.Provider value={this.state}>
<ProFile />
</UserInfo.Provider>
{/* <ProFile /> */}
{/* 如果 ProFile 放在这里 UserInfo.Provider 组件所处的树中没有匹配到 Provider 时就会收到createContext的默认值*/}
</div>
)
}
}
- Context.Consumer
这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的 value
值等价于组件树上方离这个 context 最近的 Provider 提供的 value
值。如果没有对应的 Provider,value
参数等同于传递给 createContext()
的 defaultValue
。
import React, { Component } from 'react'
const UserInfo = React.createContext({
nickName: '匿名',
grade: -1
})
// 如果有多个context
const Theme = React.createContext({
color: "green"
})
function ProFileHeader() {
return (
// <div>
// <h2>您的昵称:</h2>
// <h2>等级:</h2>
// </div>
<UserInfo.Consumer>
{
value => {
return (
<Theme.Consumer>
{
Theme => {
return (
<div>
<h2 style={{ color: Theme.color }}>您的昵称:{value.nickName}</h2>
<h2> 等级:{value.grade}</h2>
<h2> 颜色:{Theme.color}</h2>
</div>
)
}
}
</Theme.Consumer>
)
}
}
</UserInfo.Consumer>
)
}
function ProFile() {
return (
<div>
<ProFileHeader />
</div>
)
}
export default class App extends Component {
constructor() {
super();
this.state = {
nickName: "权爷",
grade: 100
}
}
render() {
return (
<div>
<UserInfo.Provider value={this.state}>
<Theme.Provider value={{ color: "red" }}>
<ProFile />
</Theme.Provider>
</UserInfo.Provider>
</div>
)
}
}
9.组件的嵌套
组件之间存在互相嵌套的关系:
import React, { Component } from 'react'
function Header() {
return <h2>我是Header组件</h2>
}
function Main() {
return (
<div>
<h2>我是Main组件</h2>
<Banner />
<ProductList />
</div>
)
}
function Footer() {
return <h2>我是Footer组件</h2>
}
function Banner() {
return (
<div>banner组件</div>
)
}
function ProductList() {
return (
<ul>
<li>产品列表1</li>
<li>产品列表2</li>
<li>产品列表3</li>
<li>产品列表4</li>
<li>产品列表5</li>
</ul>
)
}
export default class App extends Component {
render() {
return (
<div>
<Header />
<Main />
<Footer />
</div>
)
}
}
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。