React&Vue 系列:props

1,431 阅读8分钟

03_logo_490_230.png

背景:作为使用三年 react.js 的搬运工,目前正在积极学习 vue.js 语法,而在学习的过程中,总是喜欢和 react.js 进行对比,这里也不是说谁好谁坏,而是怀有他有我也有,他没我还有的思想去学习,去总结。

  • React18
  • Vue3

在前面的两篇中,

介绍了 React 和 Vue 中的变量及监听方式,这里的变量也就是指所谓的 state。但是经历过开发的人都知道,还是一种变量是 props,从父组件传递下来的变量。本章就来分析变量:props

在开始之前,先了解一下 props(说说你对 props 的理解):

  1. props 是组件之间的内置属性

  2. props 只读性(单项数据流)

  3. 为什么有了 state,还需要 props 呢?

    state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据。 而子组件只能通过 props 来传递数据。

文章的编写方向:

  • 基本使用
  • 默认值
  • 类型验证(js 版本)
  • 类型验证(ts 版本)

既然知道了方向,就正式开始了哈。

还是老规矩吧,熟悉 React,先从 React 开始。

React: props

props 出现与组件之间的,先定义好两个函数组件吧。

const Son = () => {
  return <h2>我是子组件</h2>;
};
​
const Father = () => {
  return (
    <>
      <h2>我是父组件</h2>
      <Son />
    </>
  );
};

基本使用

传递 name 属性给子组件。

// 父组件: Father
const [name, setName] = useState('copyer')
​
// ...
​
<Son name={name}/>
// 子组件: Son
const Son = (props) => {
  const { name } = props;
  return <title>我是子组件</title>;
};

默认值

在 React 的函数组件中设置默认值有两种方式

  • defaultProps
  • ES6 的对象解构默认值(推荐,清晰)
// 第一种方式:defaultProps
const Son = (props) => {
  const { name } = props;
  return <title>我是子组件</title>;
};
​
Son.defaultProps = {
  name: "james", // name默认值
};
// 第二种方式:es6 解构默认值
const Son = (props) => {
  const { name = "james" } = props;
  return <title>我是子组件</title>;
};

类型验证(js 版本)

从个人角度来说,JS 验证,只是可能为了后面便于阅读;当类型不匹配的时候,只是报出警告 ⚠️,没有实质性拦截。也就适用于开发模式(安装在 devDependencies 即可)

React + js 也能对组件的 props 的类型进行验证,借助于一个第三方库(prop-types),所以使用之前,先安装一下。

安装

pnpm add prop-types -D

使用

import PropTypes from "prop-types";
const Son = (props) => {
  const { name = "james" } = props;
  return <title>我是子组件</title>;
};
​
Son.propTypes = {
  name: PropTypes.string, // name 是一个字符串
};

prop-types具体语法,可以自行去研究一下。

类型验证(ts 版本)

React + TS 在开发中就比较的常见了,对 props 的限制也比较友好,提供了各种泛型。 就比如最常用:FC 泛型。

type FC<P = {}> = FunctionComponent<P>;

该泛型在 React18 是做了调整的。 在 React17 中,该泛型是包含 children 属性的;但是在 React18 中已经没有了,当存在 children 时,需要自定义。

当然,也可以借用 PropsWithChildren 泛型,里面就包含了 children 属性。

当然还有一种完整的写法: React.FC<React.PropsWithChildren<T>>

import type { FC } from "react";
​
interface Props {
  name: string;
  children?: JSX.Element; // 如果存在 children 属性
}
​
// 定义方式一:FC
const Son: FC<Props> = (props) => {
  const { name = "james" } = props;
  return <title>我是子组件</title>;
};
​
// 定义方式二:直接定义
const Son = (props: Props) => {
  const { name = "james" } = props;
  return <title>我是子组件</title>;
};

这样在编译的时候,发现类型不对齐,就会报错,阻止程序运行,强制要求解决。

ReactNode、ReactElement、JSX.Element 在定义子节点的时候,类型是最容易出错的,现在的我都还整不明白。

React 中的 props 大致就这些内容了,有错误的,请指教哟。

Vue: props

跟 React 一样,先创建两个组件。

<!-- Father.vue -->
<script setup lang="ts">
import Son from "./Son.vue";
</script>
​
<template>
  <div class="father">
    <h3>我是父组件</h3>
    <Son />
  </div>
</template>
​
<!-- Son.vue -->
<script setup lang="ts"></script>
​
<template>
  <div class="son">
    <h3>我是子组件</h3>
  </div>
</template>

组件定义好之后,就可以开始正题了。 其实相信你也发现了,使用 props,主要的代码集中在子组件。因为父组件只是简单的传递一下,就没有然后了。但是对于 Vue 来说,在传递的过程中还是注意两点:

  • props 传递的方式:驼峰式还是短横线式,官方更加推荐的是短横线式,更加接近于 HTML 的书写风格。
  • 动态传递值(所有类型基本都一致,要什么传递什么,但是针对对象有一种特殊的写法)
<script>
  import { reactive, ref } from 'vue'
  const count = ref(0)
  const info = reactive({
    name: 'copyer',
    age: 18
  })
</script>
​
<template>
  <!-- 要什么传递什么即可: info 对象,数字10 -->
  <Son :count="10" :info="info"/>
​
  <!-- 特殊情况:子组件要对象内的全部解构属性 -->
  <!-- 写法一: -->
  <Son :name="info.name" :age="info.age"/>
  <!-- 写法二:针对对象(直接使用 v-bind) -->
  <Son v-bind="info"/>
</template>

基本使用(js 版本)

在上面已经了解到了父组件如何传递值,那么接下来就是重要的子组件编写了。Vue 内部提供了两种写法:

  • 数组的形式(简单,功能性弱)
  • 对象的形式(复杂,功能性强: 类型检测type,默认值default,是否必传required

而子组件又分为两种写法:

都是针对 compositions api 的写法

  • 对象形式的 setup 函数(通过属性 props 的方式)为了方便描述,下面叫做:对象式 setup
  • setup 的语法糖(通过编译器宏的形式)为了方便描述,下面叫做:语法糖 setup

其实两种声明方式背后其实使用的都是 prop 选项。

<!-- 对象式 setup -->
<script>
  export default {
    props: ["info", "count"], // 数组形式
    props: { // 对象形式
      info: {
        type: Object,
      },
      count: {
        type: Number,
        default: 7,
        required: true
      }
    }
    setup(props) {
      // setup 两个参数 props, context
      console.log(props);
    },
  };
</script>
​
​
<!-- 语法糖 setup -->
<script setup>
  // 参数为数组
  const props = defineProps(["info", "count"])
  // 参数为对象
  const props = defineProps({
    info: {
        type: Object
      },
      count: {
        type: Number,
        default: 7,
        required: true
      }
  })
</script>

细节说明:

  • type 的值为预期类型的构造函数
  • 所有的 prop 都是可选的,除非声明了 required: true
  • 几乎所有的 prop 的默认值都是 undefined,只有 type: Boolean 的 prop 是 false
  • 当传递的 props 的值就是 undefined, 则内部采用的是默认值。

基本使用(ts 版本)

子组件的写法也是分为两种,跟上面一样。

针对对象式 setup 组件 ts 的写法,只是添加了一个函数 defineComponent,其 prop 的写法跟上面的写法是基本一样的,就不多说了。

那 defineComponent 有什么作用呢?额外知识:defineComponent

既然对象式 setup 组件的写法没有发生变化,那么再来看看语法糖 setup 的写法又有什么差异呢?

还是借助于编译器宏defineProps

// Son.vue
// ts 语法,一眼就可以看出接受哪些 props,哪些是可选和必选
const props = defineProps<{
  info?: object
  count: number
}>()

设置默认值,还可以借助编译器宏withDefaults

interface Props {
  info?: object
  count: number
}
​
const props = withDefaults(defineProps<Props>(), {
  info: {},
  count: 7
})

也不难看出,setup 语法糖是真的简便,利于开发。

额外知识

jsx 转化为虚拟 dom 的源码分析

在早期的时候,发布了一篇对 jsx 转化虚拟 DOM 的源码分析:React 系列: jsx 转化为虚拟 DOM,写的不是很好,但是其中想表达的内容还是解释清楚了[偷笑]。

简单回顾一下理论知识:

  • 在 React 17 版本之前,jsx 转化采用的是 React.createElement() 进行转化。(这里也就解释了为什么 React17 版本之前的组件必须要导入 React)。
React.createElement(type, config, ...children);
// config 就是对应的属性节点
// children 子节点(如果存在多个,就是从第三个参数后面累加),
  • 在 React17 版本,官方与 Babel 进行合作,采用了一种全新的 jsx 转化(react/jsx-runtime)工具函数(在开发中不能直接使用该函数), 也间接解决了每个组件中都需要导入 React 的问题(即使没有使用);当然原来的方式也没有丢弃,还是可以正常的使用(在开发中直接使用)。
// 由编译器引入(禁止自己引入!)
import { jsx as _jsx } from "react/jsx-runtime";
_jsx(type, config, maybeKey);
​
// config 对应的属性节点(包含 children 属性)
// maybeKey 处理key值的

编译器宏

在 Vue3 中的 setup 中采用了编译器宏,先说结论,我不理解。

我在网上搜了一下,关于的定义:

是一种批量处理的称谓,简单来说就是根据定义好的规则替换一定的文本,发生在程序编译阶段。

这就是基础薄弱,遇到计算机程序名词,就一脸懵逼的样子,唉,难受。

程序是先编译后执行的。那么就可以简单的理解,在编译阶段,通过一系列的规则进行替换,然后执行的。

在这里呼喊,哪位大佬可以帮我来个具体的解释,感激不尽

defineComponent 的作用

Vue3 提供的 defineComponent 函数,没有实际的逻辑代码,只是一个提供类型推导的辅助函数,专为 ts 服务。

参数接受一个 object,原模原样的返回对象,只是在使用的时候增加了一个类型推导。

好处就是:

  • 在开发的时候,多了类型提示,便捷开发。
  • 使用 props 的时候,也能提供一个正确的类型推导。
  • 返回值是一个构造函数类型,它的实例类型是根据选项推断出的组件实例类型。这是为了能让该返回值在 TSX 中用作标签时提供类型推导支持。
  • Vue3.3 增加了函数签名,与 jsx/tsx 一起使用(也就是所谓的 h 函数)

官方解读

总结

  • props 用于组件之间进行通信的。
  • 在 React 中对 props 进行验证,JS 利用 prop-types 来进行验证;ts 就是内部提供的各种泛型来进行验证的(比如: FC)
  • 在 Vue 中对 props 验证是内部提供一套书写规则即可验证;其中也分为两种:对象式 setup 的 props 属性 和 语法糖 setup 的编译器宏 defineProps() 。

继续学习 Vue3 的新语法,持续加油。