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组件通信案例

效果:

React组件化开发(6)- 组件之间的通信(3)

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.组件的嵌套

组件之间存在互相嵌套的关系:

React组件化开发(6)- 组件之间的通信(3)

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>
    )
  }
}