父子组件通信

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-05-26

父子组件通过props传值元素(使用函数)

class ChildInput extends Component {
  render(){
    let pInput = this.props.parentIpt //2.2子组件接受父组件暴露的元素
    return (
      <input type="text" ref={el=>this.props.getChildInput(el)} />
      // 1.2子组件通过el传值给props接受到的函数 将input暴露给父组件 ref中函数的形参就是本身元素的指针el
    )
  }
}

class ParentInput extends Component {
  render(){
    // 1.1通过传递一个赋值接受ref的函数给子组件
    return (
      <div>
        <input type="text" value="2.1 父组件把自己的dom元素暴露给子组件" ref={el=>this.myInput = el}/>
        <ChildInput
          getChildInput={this.getChildInput.bind(this)} 
          parentIpt = {this.myInput}
          ref={ el => this.chilidComp = el}
        >
        </ChildInput>
        <button onClick={this.setFocus.bind(this)}>点击让子组件的input聚焦</button>
      </div>
    )
    // 3. 可以暴露这个组件给父组件 像上面的ref={el=>this.chilidComp=el}一样 但是不介意使用
    // 因为这样 父组件中的chilidComp这个变量代表了整个子组件 会占用很大的空间 性能不好
  }

  getChildInput(el){
    // 1.3子组件执行render的时候 就会触发传过去的getChildInput函数 把el作为参数传到父组件
    // 这样父组件就取到了子组件暴露的dom元素 然后赋值给自己的变量
    this.cInput = el;
  }

  setFocus(){
    this.cInput.focus()
  }
}

父子组件内容分发

子组件中可以使用this.props.children拿到父组件传入的插槽:

  • 如果父组件没有设置插槽,该属性为 undefined
  • 如果父组件设置的插槽只有1个元素,就是一个对象;如果设置的插槽有多个元素,就是一个数组对象。

使用代码如下:

class Child extends Component {
  render(){
    return (
      <div>
        {this.props.children} ---将整个分发的内容放在这里
      </div>
    )
  }
}

class Parent extends Component {
  render(){
    return (
      <div>
        <Child>
          <p>父组件中的p元素</p>
          <span>父组件中的span元素</span>
        </Child>
      </div>
    )
  }
}
// 或者传一个虚拟dom对象给子组件 做分发
class Child extends Component {
  render(){
    return (
      <div>子组件的div
        {this.props.headDom} --- 展示父组件给的h1
      </div>
    )
  }
}

class Parent extends Component {
  render(){
    return (
      <div>
        <Child headDom={<h1> 给你一个h1去展示吧 </h1>}></Child>
      </div>
    )
  }
}

具名插槽

class Child extends Component {
  render(){
    return (
      <div>
        <h4>放上面的插槽</h4>
        {
          // 可以放在外面判断,这里写只是更直接的表达意思而已
          // 如果只设置了一个插槽 还要处理对象为这样 [this.props.children]
          this.props.children && Array.isArray(this.props.children) 
            ? this.props.children.filter(item => item.ref === 'top')[0]
            : ''
        }
        <hr/>
        <h4>放下面的插槽</h4>
        {
          this.props.children && Array.isArray(this.props.children) 
            ? this.props.children.filter(item => item.ref === 'bottom')[0]
            : ''
        }
      </div>
    )
  }
}

class Parent extends Component {
  render(){
    return (
      <div>
        <Child>
          <div ref="top">我是上面的插槽</div>
          <div ref="bottom">我是下面的插槽</div>
        </Child>
      </div>
    )
  }
}

Provider&Consumer – 用于嵌套过深的组件传值 避免一层层的传递

// 有三个组件 A 包含 B ,B包含 C 
// 如果A组件想传值给C组件 要使用props传给B 再传给C
// 这种传值 可以使用 Provider&Consumer 来简化

const ValContext = React.createContext('默认值')
// 或者
const { Provider, Consumer } = React.createContext('默认值')
// 这样下面 就可以使用Provider Consumer 来充当根元素 而不是 ValContext.Provider

// 提供数据的组件ValContext.Provider 供应者
// 需要接受数据的组件的根元素 使用ValContext.Consumer 消费者

class Ccom extends Component {
  render() {
    return (
      <ValContext.Consumer>
        展示从A传下来的值 函数中的参数value就是传来的值 
        函数返回一个虚拟dom元素 就可以展示传来的值在该组件上了
        {value => <p>我接受到A传来的值 : {value} </p>}
      </ValContext.Consumer>
    )
  }
}

class Bcom extends Component {
  render() {
    return (
      <Ccom></Ccom>
    )
  }
}

class Acom extends Component {
  render() {
    return (
      <ValContext.Provider value="我要传给c的值">
        <Bcom></Bcom>
      </ValContext.Provider>
    )
  }
}

Fragments – 可以聚合一个子元素列表 并不添加额外节点

看起来就是一个空标签,类似vue中的template充当根标签 但是不增加额外节点

render() {
  return (
    <>
      <ChildA/>
      <ChildB/>
    </>
  )
}

// 或者 因为空标签语法不接受 key 如果你要遍历的话 就会报警告 
// React.Fragment 能够接收key 也是唯一只能接受key

render(){
  return (
    {
      this.list.map(item=>(
        <React.Fragment key={item.id}>
          <ChildA/>
          <ChildB/>
        </React.Fragment>
      ))
    }
  )
}

Portals – 将子节点渲染到父组件之外的节点上

比如一个有遮罩的模态框,必然需要添加在body上。

const bodyEl = document.getElementsByTags('body')[0]

class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }

  componentDidMount() {
    bodyEl.appendChild(this.el);
  }

  componentWillUnmount() {
    bodyEl.removeChild(this.el);
  }

  render() {
    return ReactDOM.createPortal(
      this.props.children, // 下面的Modal中的Child
      this.el,
    );
  }
}

// 将模态框使用在父组件中  此时的Modal组件相当于一个中转 使用Portal则挂载在父组件上但可以不受父组件根元素div的限制
// 然后再modal中将child(即模态框的实际内容)添加在创建的div上 再将div(模态框)添加到body上
class Parent extends React.Component {
  render() {
    return (
      <div>
        <Modal>
          <Child />
        </Modal>
      </div>
    );
  }
}

function Child() { // 无状态组件 
  return (
    <div className="modal">
      <button>Click</button>
    </div>
  );
}

高阶组件

高阶组件就是一个函数,且该函数接受一个组件作为参数,然后返回一个新的组件。相当于是一个工厂用来增强组件的,a,b组件都需要一把锤子,就将其传值给锤子工厂(高阶组件)去给a,b组件装一把锤子,这里的锤子就是公共部分,a,b都需要用到的。

高阶组件的参数:需要混入公共功能的组件

高阶组件的返回:已经增强过的组件

class Acom extends React.Component {
  render() {
    return (
      <div>
        <p>我是a我需要一把{this.props.wuqi}</p>
        <button onClick={this.props.show}>展示锤子吧!!!</button>
      </div>
    );
  }
}

class Bcom extends React.Component {
  render() {
    return (
      <div>
        <p>我是b我也需要一把{this.props.wuqi}</p>
        <button onClick={this.props.show}>展示锤子吧!!!</button>
      </div>
    );
  }
}

function myHighCom(Com){
  // 匿名类 相当于匿名函数
  return class extends React.Component {
    constrcutor(props){
      super(props)
      this.state = {
        wuqi: "锤子"
      }
    }

    render() {
      return <Com wuqi={this.state.wuqi} show={this.show}></Com>
    }

    show() {
      console.log('展示我的锤子')
    }

    componentDidMount(){
      console.log('我们在生命周期钩子函数里面也要做同样的事情')
    }
  }
}

const AplusCom = myHighCom(Acom)
const BplusCom = myHighCom(Bcom)

// 使用增强过的组件
class Parent extends React.Component {
  render() {
    return (
      <div>
        <AplusCom/>
        <hr/>
        <BplusCom/>
      </div>
    );
  }
}

ReactDOM.render(<Parent />, document.getElementById('app'))