02|搞定 Props

156 阅读4分钟

props 在 React 中是不可或缺的,我们在开发React应用时随处可见 props 的身影。它可以作为原生组件的属性设置,也是作为组件之间的数据,事件等通信传递的重要手段。本节我们一起来看看什么是 props,以及如何使用props,让我们一起搞定 props ~

什么是Props

还记得第一节中我们的小作业,打印 jsx 对象吗?如果你没有完成也没关系,我们现在就来看下面的这样的一个 jsx 代码打印出来是怎样的。

function App() {

  const handler = () => {
    console.log('当前时间', new Date())
  }
  const jsx = (
    <div
      className="app"
      style={{ background: '#1890FF', color: '#FFF' }}
      onClick={handler}
    >
      <div>点击在控制台查看时间</div>
      <p>我是来凑数的,点我也能看时间</p>
      <Child msg="开心往前飞" />
    </div>
  )
  console.log(jsx)
  return jsx
}

function Child(props: { msg: string }) {

  return (
    <div>
      <div>其实点子组件也行的~</div>
      <div>父组件穿过来的消息是:{props.msg}</div>
    </div>
  )
}

我们打开浏览器控制台查看打印信息如下👇 image.png 这里我们可以看到的是className = "app"的 div 组件,props 内包含着如下内容:

  1. 写在标签内部的 3 个组件,它们组成了children这个数组(还记得第一节说过的“React.createElement 的第三个及后续参数就是组件的子组件,即 children”吗?)
  2. 写在 div 组件上的classNamestyle以及onClick三个属性

那么因为我们这里看的是原生组件(就是 html 的那些标签元素)这些属性在 react 内部都会做相应处理,使其最终成为 html 元素。

我们来看一下封装的 Child 组件👇

image.png

它的 type 其实就是 Child 这个函数(还未执行)对于 react 来说可以用来判断它不是原生组件。此时我们看到的组件还未执行,但是最终会将 props 传给这个函数执行最后得到dom渲染到页面上(尽管实际过程要比这复杂得多)。那实际上,它就是个 function 嘛,我们直接执行不就完事儿了嘛~ 我们将代码修改如下👇


  const jsx = (
    <div
    >
      {/* 实际上这两者最后得到的结果一致 */}
      <Child msg="开心往前飞"/>
      {Child({msg: "开心往前飞飞飞"})}
    </div>
  )

image.png

我们可以看到,第二种方式就是直接执行Child函数拿到返回到 jsx 对象,所以对于 FunctionComponent 来说 props 的本质其实就是函数的传参,那么也不难想象 ClassComponent 就是在 new 时将 props 放到 this 上。

class ClassDemo extends React.Component<{ msg: string }>  {
  render() {
    return (
      <div>ClassMsg{this.props.msg}</div>
    )
  }
}

function App() {
  const jsx = (
    <div>
      <ClassDemo msg="飞呀飞呀飞" />
    </div>
  )
  return jsx
}

以下源码证实确实如此,其中 Component 其实就是我们写 ClassComponent 时需要 extends 的那个 React.Component。

image.png

如此,我们就能明白了,props 对于我们封装的函数组件而言就是函数的参数。而 ClassComponent 就会在new时会将其绑定到props上。

如何使用

既然我们知道了 props 的本质就是一个对象(这里仍在强调 React 的本质还是 JavaScript )。那么我们就知道如何来灵活运用了,我们一起来看几种 props 的使用方式。

以下demo出自于林哥的掘金小册《React 进阶实践指南》props 章节
PS:因为自己实在想不到有比这个写的更好的demo了,所以“窃”了过来 🤣


function App() {
  
  const handler = (msg: string) => {
    console.log('父组件中的事件被调用了,子组件发来消息体为:' + msg)
  }
  
  return (
    <div>
      <PropsComponent
        msg='消息来自父组件' // 1. 传递数据
        handler={handler} // 2. 传递回调函数
        Component={Comp} // 3. 传递组件
        renderFunction={() => <div>render function</div>} // 4. 传递渲染函数
      >
        {/* 在标签内部的属于 children */}
        {/* 5. 在 children 中传递渲染函数,跟第四点一样,不过在children上 */}
        {(childMsg: string) => <div>渲染子组件传过来的消息:{childMsg}</div>}
        {/* 6. 插槽组件 */}
				<div>children</div>
      </PropsComponent>
    </div>
  )
}

// 定义的 props 的类型
interface Props {
  msg: string
  handler: (msg: string) => void
  Component: React.ComponentType<any>
  renderFunction: () => React.ReactNode
  children?: (((childMsg: string) => React.ReactNode) | React.ReactNode)[]
}

function PropsComponent(props: Props) {
  const { msg, handler, Component, renderFunction, children } = props

  return (
    <div>
      {/* 1. 使用父组件传递的数据 */}
      <div>{msg}</div>
      {/* 2. 使用父组件传递过来的函数 */}
      <div onClick={() => handler('来自子组件的消息')}>点击子组件给父组件发消息</div>
      {/* 3. 使用父组件传递过来的组件 */}
      <Component />
      {/* 4. 使用父组件传递过来的渲染函数 */}
      {renderFunction()}
      {children && children.map((item) => {
        if (typeof item === 'function') {
          return item('子组件传递的消息') // 5. 如果是函数,则调用函数
        }
        return item // 6. 如果是其它类型插槽组件,则直接渲染
      })}
    </div>
  )
}

function Comp() {
  return (<div>comp 组件</div>)
}

我们一起来看一下Child组件的 props 在运行时的表现,以加深印象 image.png

pendingProps 这个对象包含着我们传的参数。最后这个对象会被传入到 PropsComponent 函数中作为形参 props 供子组件消费。

总结

props 说到底不过是JavaScript中函数的参数罢了,只要理解了这一点,也就没什么难度了。