props和模拟插槽以及父子组件通信

163 阅读8分钟

props

在react中,一切写在组件上的属性和子节点都被视为props。react中父子传值、插槽全都是基于props,不像vue有事件监听、emit以及专门的插槽slot。

在类组件的constructor构造函数获取props

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "hello"
  }
  // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
  render() {
    return (
      <div>
        <h1>App组件</h1>
        <Son a="123" b="456" msg={this.state.msg}>
          <div>Panda</div>
        </Son>
      </div>
    )
  }
}

export default App;

// son.js
class Son extends React.Component {
    // 类组件当有constructor构造函数时,props在constructor函数中接受
    constructor(props) {
        super(props)
        console.log(props)
        this.state = {
            sonMsg: "KONGFU"
        }
    }
    render() {
        return (
            <div>
                {this.state.sonMsg}
                <h1>Son组件</h1>
            </div>
        )
    }
}
export default Son;

image.png

ab是组件Son的属性,<div>Panda</div>是组件Son的子节点,msg是组件Son的父组件给组件Son传递的值。它们在组件Son的props中获取。

不写constructor构造函数获取props

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "hello"
  }
  render() {
    return (
      <div>
        <h1>App组件</h1>
        <Son a="123" b="456" msg={this.state.msg}>
          <div>Panda</div>
        </Son>
      </div>
    )
  }
}

export default App;
// son.js
import React from "react";
class Son extends React.Component {
    state = {
        sonMsg: "KONGFU"
    }
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        console.log(this.props);
        return (
            <div>
                {this.state.sonMsg}
                <h1>Son组件</h1>
            </div>
        )
    }
}
export default Son;

image.png

props的类型验证和默认传值

类型验证:设置静态属性propTypespropTypes是类的属性,而不是类实例的属性),并编写每个属性的验证方法。

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: 666
  }
  render() {
    return (
      <div>
        <h1>App组件</h1>
        <Son a="123" b="456" msg={this.state.msg}>
          <div>Panda</div>
        </Son>
      </div>
    )
  }
}

export default App;
// son.js
import React from "react";
class Son extends React.Component {
    state = {
        sonMsg: "KONGFU"
    }
    // 静态属性
    static propTypes = {
        msg:(props) => {
            // 属性的验证方法,props是整个props
            if(typeof props.msg !== "string"){
                console.warn("msg必须是字符串")
            }
        }
    }
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        console.log(this.props);
        return (
            <div>
                {this.state.sonMsg}
                <h1>Son组件</h1>
            </div>
        )
    }
}
export default Son;

传入数字666控制台打印了属性验证方法中的警告

image.png

默认值:设置静态属性defaultPropsdefaultProps是类的属性,而不是类实例的属性),并编写每个属性的默认值。

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: 666
  }
  render() {
    return (
      <div>
        <h1>App组件</h1>
        <Son a="123" b="456">
          <div>Panda</div>
        </Son>
      </div>
    )
  }
}

export default App;
// son.js
import React from "react";
class Son extends React.Component {
    state = {
        sonMsg: "KONGFU"
    }
    // 静态属性
    static propTypes = {
        msg:(props) => {
            // 属性的验证方法,props是整个props
            if(typeof props.msg !== "string"){
                console.warn("msg必须是字符串")
            }
        }
    }
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        console.log(this.props);
        return (
            <div>
                {this.state.sonMsg}
                <h1>Son组件</h1>
            </div>
        )
    }
}
export default Son;

在App.js中没有给Son组件传msg,props就获取到了设置的msg的默认值

image.png

proptypes库

在react中,可以使用proptypes库快速编写props的验证,不用自己一个一个去定义props的验证方法。

安装:

npm i proptypes

使用:

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: 666
  }
  render() {
    return (
      <div>
        <h1>App组件</h1>
        <Son a="123" b="456" msg={this.state.msg}>
          <div>Panda</div>
        </Son>
      </div>
    )
  }
}

export default App;

// son.js
import React from "react";
// 引入proptypes
import proptypes from 'proptypes'
class Son extends React.Component {
    state = {
        sonMsg: "KONGFU"
    }
    // 静态属性
    static propTypes = {
        // 使用proptypes的string方法验证是不是字符串
        msg:proptypes.string
    }
    static defaultProps = {
        msg:"i am gloria",
    }
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        console.log(this.props);
        return (
            <div>
                {this.state.sonMsg}
                <h1>Son组件</h1>
            </div>
        )
    }
}
export default Son;

image.png

控制台抛出了警告

模拟vue中的插槽

插槽本质上是子组件的html内容需要父组件传入。在JSX的加持下,可以把html像字符串和数字一样传递,所以插槽只需要直接作为props传入即可。

react中本身没有插槽,但是可以模拟插槽。实现方法:把要插入的html元素作为props传递给子组件,子组件渲染传递过来的props

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "6 = 110"
  }
  render() {
    return (
      <div>
        <h1>App组件</h1>
        <Son a="123" b="456" msg={this.state.msg}>
          <div>Panda,模拟vue的slot</div>
        </Son>
      </div>
    )
  }
}

export default App;
// son.js
import React from "react";
// 引入proptypes
import proptypes from 'proptypes'
class Son extends React.Component {
    state = {
        sonMsg: "KONGFU"
    }
    // 静态属性
    static propTypes = {
        // 使用proptypes的string方法验证是不是字符串
        msg:proptypes.string
    }
    static defaultProps = {
        msg:"i am gloria",
    }
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        console.log(this.props);
        return (
            <div>
                {this.state.sonMsg}
                <h1>Son组件</h1>
                {/* 模拟的vue的插槽 */}
                {this.props.children}
            </div>
        )
    }
}
export default Son;

image.png

模拟具名插槽

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "6 = 110"
  }
  render() {
    return (
      <div>
        <h1>App组件</h1>
        <Son a="123" b="456" msg={this.state.msg} c={<div>具名插槽,插槽名为c</div>}>
          <div>Panda,模拟vue的slot</div>
        </Son>
      </div>
    )
  }
}

export default App;
// son.js
import React from "react";
// 引入proptypes
import proptypes from 'proptypes'
class Son extends React.Component {
    state = {
        sonMsg: "KONGFU"
    }
    // 静态属性
    static propTypes = {
        // 使用proptypes的string方法验证是不是字符串
        msg:proptypes.string
    }
    static defaultProps = {
        msg:"i am gloria",
    }
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        console.log(this.props);
        return (
            <div>
                {this.state.sonMsg}
                <h1>Son组件</h1>
                {/* 模拟的vue的插槽 */}
                {this.props.children}
                {this.props.c}
            </div>
        )
    }
}
export default Son;

image.png

模拟作用域插槽

作用域插槽:允许子组件向父组件传递数据,使父组件能够使用子组件的数据。

在react中实现作用域插槽:在父组件中使用子组件时在props中传递一个方法,然后在子组件中通过props调用这个方法,调用时就可以将子组件的数据作为参数传递给父组件了。

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "6 = 110"
  }
  render() {
    return (
      <div>
        <h1>App组件</h1>
        {/* scope就是作用域插槽中子组件向父组件传递数据,在返回的html元素中就可以使用子组件传递的这个数据 */}
        <Son a="123" b="456" msg={this.state.msg} c={<div>具名插槽,插槽名为c</div>} scopeSlot={(scope)=>{return <div>{scope}</div>}}>
          <div>Panda,模拟vue的slot</div>
        </Son>
      </div>
    )
  }
}

export default App;
// son.js
import React from "react";
// 引入proptypes
import proptypes from 'proptypes'
class Son extends React.Component {
    state = {
        sonMsg: "KONGFU"
    }
    // 静态属性
    static propTypes = {
        // 使用proptypes的string方法验证是不是字符串
        msg: proptypes.string
    }
    static defaultProps = {
        msg: "i am gloria",
    }
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        console.log(this.props);
        return (
            <div>
                {this.state.sonMsg}
                <h1>Son组件</h1>
                {/* 模拟的vue的插槽 */}
                {this.props.children}
                {this.props.c}
                {/* 作用域插槽,子组件给父组件传递的数据作为参数传入 */}
                {this.props.scopeSlot(this.state.sonMsg + "子组件传递的数据")}
            </div>
        )
    }
}
export default Son;

image.png

父子组件通信

父子组件通信方式:

  1. 传递数据(父传子)与传递方法(子传父)
  2. ref标记(父组件拿到子组件的引用,从而调用子组件的方法)

父组件给子组件传值

父传子使用 props属性,传入props。

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "6 = 110"
  }
  render() {
    return (
      <div>
        <h1>App组件</h1>
        {/* msg是父组件给子组件传值 */}
        <Son msg={this.state.msg}></Son>
      </div>
    )
  }
}

export default App;
// son.js
import React from "react";
// 引入proptypes
import proptypes from 'proptypes'
class Son extends React.Component {
    state = {
        sonMsg: "KONGFU"
    }
    // 静态属性
    static propTypes = {
        // 使用proptypes的string方法验证是不是字符串
        msg: proptypes.string
    }
    static defaultProps = {
        msg: "i am gloria",
    }
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        return (
            <div>
                {/* 子组件使用父组件传递的数据 */}
                {this.props.msg}
            </div>
        )
    }
}
export default Son;

image.png

子组件给父组件传值

在vue中,父组件传递给子组件的props遵循数据单向流,它是不能在子组件内部进行props的修改,需要在子组件内部触发父组件自定义的一个事件,在父组件中这个自定义事件的事件回调函数中接受子组件传递的数据,然后修改。

在React 中同理,数据流也是单向的,这意味着数据总是从父组件流向子组件。父组件通过 props(属性)将数据传递给子组件,而子组件则可以通过回调函数通知父组件发生的变化。

数据流是单向举例说明

举例说明数据流是单向的,比如父组件修改状态,传入子组件的数据也会被修改,父组件和子组件页面的值都会刷新。但子组件修改父组件的数据是不可行的,在React中的子组件页面不会刷新而且还会报错。

父组件修改状态:

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "父组件的数据"
  }
  change = () => {
    this.setState({
      msg: "父组件数据被修改"
    });
  }
  render() {
    return (
      <div>
        <h1>父组件</h1>
        <p>{this.state.msg}</p>
        <button onClick={this.change}>change</button>
        <hr />
        <Son msg={this.state.msg}></Son>
      </div>
    )
  }
}

export default App;
// son.js
import React from "react";
// 引入proptypes
import proptypes from 'proptypes'
class Son extends React.Component {
    state = {
        sonMsg: "KONGFU"
    }
    // 静态属性
    static propTypes = {
        // 使用proptypes的string方法验证是不是字符串
        msg: proptypes.string
    }
    static defaultProps = {
        msg: "i am gloria",
    }
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        return (
            <div>
                <h1>子组件</h1>
                <p>{this.props.msg}</p>
            </div>
        )
    }
}
export default Son;

2.gif

子组件修改父组件的数据:

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "父组件的数据"
  }
  change = () => {
    this.setState({
      msg: "父组件数据被修改"
    });
  }
  render() {
    return (
      <div>
        <h1>父组件</h1>
        <p>{this.state.msg}</p>
        <button onClick={this.change}>changefromfather</button>
        <hr />
        <Son msg={this.state.msg}></Son>
      </div>
    )
  }
}

export default App;
import React from "react";
// 引入proptypes
import proptypes from 'proptypes'
class Son extends React.Component {
    state = {
        sonMsg: "KONGFU"
    }
    changefromson = () => {
        this.props.msg = '子组件修改父组件';
    }
    // 静态属性
    static propTypes = {
        // 使用proptypes的string方法验证是不是字符串
        msg: proptypes.string
    }
    static defaultProps = {
        msg: "i am gloria",
    }
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        return (
            <div>
                <h1>子组件</h1>
                <p>{this.props.msg}</p>
                <button onClick={this.changefromson}>changefromson</button>
            </div>
        )
    }
}
export default Son;

2.gif

子组件传值给父组件正确方法

实现方法:将父组件修改state状态的方法作为属性传值给子组件,子组件调用传入的函数,调用时将修改的数据作为参数传给父组件。

// App.js
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "父组件的数据"
  }
  change = () => {
    this.setState({
      msg: "父组件数据被修改"
    });
  }
   // 父组件接收消息的方法
  sonchange = (arg) => {
    // 更新状态
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        <h1>父组件</h1>
        <p>{this.state.msg}</p>
        <button onClick={this.change}>changefromfather</button>
        <hr />
        {/* 传递方法作为 props */}
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;
// son.js
import React from "react";
// 引入proptypes
import proptypes from 'proptypes'
class Son extends React.Component {
    state = {
        sonMsg: "KONGFU"
    }
    changefromson = () => {
        // 调用父组件传递的方法
        this.props.fatherfn('子组件修改父组件的数据');
    }
    // 静态属性
    static propTypes = {
        // 使用proptypes的string方法验证是不是字符串
        msg: proptypes.string
    }
    static defaultProps = {
        msg: "i am gloria",
    }
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        return (
            <div>
                <h1>子组件</h1>
                <p>{this.props.msg}</p>
                <button onClick={this.changefromson}>changefromson</button>
            </div>
        )
    }
}
export default Son;

2.gif

在vue中是自定义事件,让子组件传入参数然后去触发父组件的修改数据的事件函数,父组件接受参数来修改数据,在react中是将函数传给子组件然后直接调用父组件的函数,将修改的数据作为参数。