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>
)
}
我们打开浏览器控制台查看打印信息如下👇
这里我们可以看到的是
className = "app"的 div 组件,props 内包含着如下内容:
- 写在标签内部的 3 个组件,它们组成了
children这个数组(还记得第一节说过的“React.createElement 的第三个及后续参数就是组件的子组件,即 children”吗?) - 写在 div 组件上的
className、style以及onClick三个属性
那么因为我们这里看的是原生组件(就是 html 的那些标签元素)这些属性在 react 内部都会做相应处理,使其最终成为 html 元素。
我们来看一下封装的 Child 组件👇
它的 type 其实就是 Child 这个函数(还未执行)对于 react 来说可以用来判断它不是原生组件。此时我们看到的组件还未执行,但是最终会将 props 传给这个函数执行最后得到dom渲染到页面上(尽管实际过程要比这复杂得多)。那实际上,它就是个 function 嘛,我们直接执行不就完事儿了嘛~ 我们将代码修改如下👇
const jsx = (
<div
>
{/* 实际上这两者最后得到的结果一致 */}
<Child msg="开心往前飞"/>
{Child({msg: "开心往前飞飞飞"})}
</div>
)
我们可以看到,第二种方式就是直接执行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。
如此,我们就能明白了,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 在运行时的表现,以加深印象
pendingProps 这个对象包含着我们传的参数。最后这个对象会被传入到 PropsComponent 函数中作为形参 props 供子组件消费。
总结
props 说到底不过是JavaScript中函数的参数罢了,只要理解了这一点,也就没什么难度了。