# 一、props 属性

# 1、props 传值

  react 中父向子传递数据和 vue 类似,我们可以通过

在父组件中的子组件标签上添加 自定义属性

然后子组件通过 constructor(props)获取数据

  • 父组件
class App extends React.Component {
  constructor() {
    this.state = {
      user: 'lencamo'
      // info: {
      //   //
      // }
    }
  }
  //
  render() {
    const { user } = this.state

    return <HelloWorld userName={user} />
    // 批量传值
    // return <HelloWorld {...this.state.info} />
  }
}
  • 子组件
class HelloWorld extends React.Component {
  // 默认可以省略
  // constructor(props) {
  //   super(props)
  // }
  //
  render() {
    const { userName } = this.props

    return <h2>{userName}</h2>
  }
}

  当然,在 vue 中 props 选项的数据是可以进行类型验证的,并还支持数组和对象两种方式

在 react 项目中,如果使用了 Flow 或者 Typescript 那么可以直接进行类型验证

如果没有的话,可以使用 prop-types (opens new window) 库来进行参数验证,具体使用可以参考 使用 PropTypes 进行类型检查 (opens new window)

props 类型检测
import PropTypes from 'prop-types'

class HelloWorld extends React.Component {
  render() {
    const { userName } = this.props

    return <h2>{userName}</h2>
  }
}

// props 类型验证
HelloWorld.propTypes = {
  userName: PropTypes.string.isRequired,
  barList: PropTypes.array
}

// props 默认值
HelloWorld.defaultProps = {
  userName: '用户123'
}

# 2、props 回调

  在 vue 中,我提到过,使用 v-on 和使用 props 的区别。

一个通过 props 传递回调函数供子组件调用

另外一个是通过语法糖 v-on 为子组件 vc 身上绑定自定义事件供子组件通过$emit 触发。

  在 react 中没有过多的语法糖,所以采用的是 props 的方式:

  • 父组件
class App extends React.Component {
  constructor() {
    this.state = {
      count: 1
    }
  }

  // 创建回调
  countChange(n) {
    this.setState({
      count: this.state.counter + n
    })
  }

  render() {
    const { count } = this.state

    return (
      <>
        <h2>当前计数:{count} </h2>
        <HelloWorld countChange={(n) => this.countChange(n)} />
      </>
    )
  }
}
  • 子组件
class HelloWorld extends React.Component {
  // 触发回调
  addClick(n) {
    this.props.countChange(n)
  }

  render() {
    return (
      <div>
        <button onClick={() => addClick(5)}>+5</button>
        <button onClick={() => subClick(-3)}>-3</button>
      </div>
    )
  }
}

# 二、react 插槽

  react 中其实是没有插槽的,只不过是我们想在 react 实现类似于 vue 中的插槽功能。

优点:开发者自己能实现的的简单功能,就不要干涉,让开发者自由发挥

# 1、props 与 children

const element = createElement(type, props, ...children)

  前面我们使用子组件时,采用的都单标签。实际上,我们使用可以使用双标签的,并且标签内的元素可以在 this.props.children 中找到。

每个组件都可以获取到 props.children。它包含组件的开始标签和结束标签之间的内容。

  • 父组件
children 与 单个子元素 ✍

  当传递单个子元素时,children 并不是一个数组,而是子元素本身

具体可以查看:react 源码实现 (opens new window)

NavBar.propTypes = {
  children: PropTypes.element
}
class App extends React.Component {
  //
  render() {
    return (
      <div>
        <NavBar>
          <button>左箭头</button>
          <input placeholder="请输入" />
          <button>搜索</button>
        </NavBar>
      </div>
    )
  }
}
  • 子组件
class NavBar extends React.Component {
  render() {
    const { children } = this.props

    return (
      <div className="nav-bar">
        <div className="left">{children[0]}</div>
        <div className="center">{children[1]}</div>
        <div className="right">{children[2]}</div>
      </div>
    )
  }
}

# 2、props 与 DOM

  当面的第一种方法,已经体验完了,但是要注意只传递一个元素的情况。

  下面我们看看,另外一种: 直接通过 props 向子组件传递元素

  • 父组件
class App extends React.Component {
  //
  render() {
    return (
      <div>
        <NavBar
          letfSlot={<button>左箭头</button>}
          centerSlot={<input placeholder="请输入" />}
          rightSlot={<button>搜索</button>}
        />
      </div>
    )
  }
}
  • 子组件
class NavBar extends React.Component {
  render() {
    const { letfSlot, centerSlot, rightSlot } = this.props

    return (
      <div className="nav-bar">
        <div className="left">{letfSlot}</div>
        <div className="center">{centerSlot}</div>
        <div className="right">{rightSlot}</div>
      </div>
    )
  }
}

# 3、作用域插槽 *

简单一句话:使用回调函数(向子组件传递元素,并获取子组件数据)

  • 父组件
class App extends React.Component {
  constructor() {
    //
    this.state = {
      titles: [
        { id: 1, title: '' },
        { id: 2, title: '' },
        { id: 3, title: '' },
        { id: 4, title: '' }
      ]
    }
  }
  //
  render() {
    return (
      <div>
        {/* 使用回调获取子组件数据 */}
        <ItemList title="titles" itemSlot={(msg) => <p>{msg}</p>} />
      </div>
    )
  }
}
  • 子组件
class ItemList extends React.Component {
  constructor() {
    //
    this.state = {
      msgs: ['', '', '', '']
    }
  }

  render() {
    const { titles, itemSlot } = this.props
    const { msg } = this.state

    return (
      <div>
        {msgs.map((item, index) => {
          return (
            <>
              <h2>{titles[index].title || '未找到匹配的标题'}</h2>

              {/* 使用父组件数据的同时,触发回调并向其传递数据 */}
              <div className="item-list">{itemSlot(item)}</div>
            </>
          )
        })}
      </div>
    )
  }
}

# 三、Context

  在 react 中的跨组件通信方案有:EventBus、Context、Redux 等,下面先介绍 Context 就可以了,

提示

  真实开发中,我们可以采用:

  • 方案 1:Context + props
  • 方案 2:react-Redux / react-toolkit

  Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

对比 vue 的依赖注入

  vue 中的依赖注入采用的是:(Provide、Inject)

<script>
export default {
  // provide 提供给后代✨组件的数据/方法
  // provide: {
  //   message: 'provided by father'
  // }
  provide() {
    return {
      message: 'provided by father'
    }
  }
}
</script>
<script>
export default {
  // 使用 inject 选项来接收由provide提供的、当前组件✨需要的数据/方法
  inject: ['message']
}
</script>
对比 crateRef() 使用

  createRef()是在同一组件下的用于获取 Dom 的方法

class Demo extends React.Component {
  constructor() {
    // 绑定到ref对象上
    this.bookRef = React.createRef()
  }

  getNativeDom() {
    console.log(this.bookRef.current)
  }

  render() {
    return (
      <div>
        <p ref={this.bookRef}>JavaScript高级程序设计</p>
        <p>你不知道的js</p>

        <button onClick={(e) => this.getNativeDom()}>获取DOM</button>
      </div>
    )
  }
}

  下面的这个 Context 的使用方式有点类似于 依赖注入和 crateRef()的结合体:

  • const xxxContext = createContext()

搭配 1

  • <xxxContext.Provider value={ data }></xxxContext.Provider>
  • <xxxContext.Consumer>{(value) => { // }}</xxxContext.Consumer>

搭配 2

  • <xxxContext.Provider value={ data }></xxxContext.Provider>
  • componentName.contextType = xxxContext + this.context

# 1、创建 context

  • context/index.js
import { createContext } from 'react'

const ThemeContext = createContext()

// createContext('默认值')
const UserContext = createContext('普通用户')

export { ThemeContext, UserContext }

# 2、发送数据方

import { ThemeContext } from './context/index.js'

class App extends React.Component {
  //
  render() {
    return (
      <ThemContext.Provider value={{ color: 'red', size: '33' }}>
        <HelloWorld />
      </ThemContext.Provider>
    )
  }
}

# 3、接收数据方

  • 方式 1:使用 contextType + this.context
import { ThemeContext } from './context/index.js'

// 方式1
class Soners extends React.Component {
  // static contextType = ThemeContext

  render() {
    const theme = this.context

    return <Button style={{ ...theme }} />
  }
}

// 指定 contextType 读取当前的 theme context
Soners.contextType = ThemeContext
  • 方式 2:使用<ThemeContext.Consumer> + value
import { ThemeContext } from './context/index.js'

// 方式2✨(同样适用于函数式组件)
class Soners extends React.Component {
  //
  render() {
    return (
      <ThemeContext.Consumer>
        {(value) => {
          return <Button style={{ ...value }} />
        }}
      </ThemeContext.Consumer>
    )
  }
}
更新于 : 8/7/2024, 2:16:31 PM