一种更加方便的组件开发方式
0. 配置 Vite 对 JSX 的支持
1 2 3
| npm create vite@latest cd vite-project npm install
|
将项目跑起来之后进入项目中配置 vite 对于 jsx 的支持,根据官方文档的说明,下载@vitejs/plugin-vue-jsx插件。
1
| npm install --save-dev @vitejs/plugin-vue-jsx
|
打开项目根目录下的 vite.config.js,引入并使用组件
1 2 3 4 5 6 7 8
| import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import vueJsx from "@vitejs/plugin-vue-jsx";
export default defineConfig({ plugins: [vue(), vueJsx()], });
|
至此项目配置 JSX 支持完毕。
1. 调整开发方式
1.1 调整 SFC 为 JSX
配置了 JSX 之后,我们编写组件不再使用.vue 结尾,即不再编写 SFC 组件。以下是一个 Vue3 函数式组件的示例
1 2 3 4 5 6 7 8
| import { defineComponent } from "vue";
const App = defineComponent(() => { return () => <div>App</div>; });
export default App;
|
其中 defineComponent 接受一个函数作为组件初始化函数,具体详看文档defineComponent。
1.2 调整 css 的引入方式
原来在 SFC 中,项目的样式文件仅需写在 SFC 的 style 标签中即可。但是在 JSX 中,样式文件作为独立的文件,可以 import 到任何组件中,即以下面的方式引入样式文件
1 2 3 4
| .container { color: rgba(0, 0, 0, 0.6); }
|
1 2 3 4 5 6 7 8 9
| import { defineComponent } from "vue"; import "./App.css";
const App = defineComponent(() => { return () => <div class="container">App</div>; });
export default App;
|
但是上述的使用方式,App.css 中的样式是影响全局的,我们需要配置 CSS Module 实现 SFC 中的 scoped,让组件样式的影响范围保持在组件中。
在配置 CSS Modules 之前,我们先引入 less,让 css 编写更加方便。
1.3 引入 less
在 vite 中,仅需安装 less 依赖即可添加 less 支持
1
| npm install --save-dev less
|
然后把.css 后缀改为.less 即可
1 2 3 4 5 6 7
| .container { color: rgba(0, 0, 0, 0.6); } .red { color: red !important; }
|
1 2 3 4 5 6 7 8 9
| import { defineComponent } from "vue"; import "./App.less";
const App = defineComponent(() => { return () => <div class="container">App</div>; });
export default App;
|
1.4 调整 less 的引入方式
在 vite 中,*.module.less 命名的 less 文件,vite 会对其进行 CSS Module 处理,导出一个 JS 对象,对象的 key 为 css 样式类名,对象的 value 为 css module 后的样式类名。即组件调整为以下写法。
1 2 3 4 5 6 7
| .container { color: rgba(0, 0, 0, 0.6); } .red { color: red !important; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { defineComponent } from "vue"; import styles from "./App.module.less";
const App = defineComponent(() => { return () => <div class={styles.container}>App</div>; });
export default App;
|
通过生成添加哈希的类名,保证了每个组件引用的样式都是独一无二的,避免了全局污染。
1.5 插槽
使用 JSX 后插槽和编写如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { defineComponent } from "vue"; import Foo from "./Foo";
const App = defineComponent(() => { return () => ( <Foo> {{ default: () => <div>默认插槽</div>, name: () => <div>具名插槽</div>, }} </Foo> ); });
export default App;
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { defineComponent } from "vue";
const Foo = defineComponent((props, context) => { return () => ( <div> <div>Default Slot {context.slots.default()}</div> <div>Name Slot {context.slots.name()}</div> </div> ); });
export default Foo;
|
在 Foo 组件中编写体验还好,但是在 App 组件中,插槽的使用方式不如 React 中的方便
1.6 HOC
现在我们可以编写 HOC 了,代码示例如下
1 2 3 4 5 6 7 8 9
| import { defineComponent } from "vue"; import Foo from "./Foo";
const App = defineComponent(() => { return () => <Foo msg="msg" msg2="msg2" />; });
export default App;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { defineComponent } from "vue"; import HOC from "./HOC";
const Foo = defineComponent( (props) => { return () => ( <div> <div>{props.msg}</div> <div>{props.msg2}</div> </div> ); }, { props: { msg: String, msg2: String, }, } );
export default HOC(Foo);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { defineComponent, h, useAttrs } from "vue";
const HOC = (Element) => { const Wrapper = defineComponent((props) => { const attrs = useAttrs(); return () => h(Element, { ...attrs, msg: "劫持msg", }); }); Wrapper.inheritAttrs = false; return Wrapper; };
export default HOC;
|
如上,我在 APP 组件中引入 Foo 组件,Foo 组件受到 HOC.js 的劫持,其中 props 中的 msg 会被篡改成”劫持 msg”。
HOC可以实现很多功能,在开发中很方便。
2. 函数式组件
函数式组件就是使用函数编写的一种组件,组件由一个函数构成,接收 props,返回 vnode。在 React 中通过 React Hook 可以编写出很多功能强大的组件。但是在 Vue 中,函数式组件没有生命周期,无法使用 Vue 的 Hooks。
以下是一个函数式组件的例子
1 2 3 4
| const App = () => <div>App</div>;
export default App;
|
仅仅编写一个函数,就能成为一个组件。
但如果是 React 的话,可以添加 Hook 在不同的时机触发不同的钩子,代码如下
1 2 3 4 5 6 7 8 9 10 11 12
| import React, { useEffect } from "react";
const App = () => { useEffect(() => { consol.log("load"); }, []);
return <div>App</div>; };
export default App;
|
但是在 Vue 中,函数式组件不具备任何生命周期,无法使用 Hooks,如果想实现上述方式,得在 setup 中注册钩子,代码如下
1 2 3 4 5 6 7 8 9 10 11
| import { defineComponent, onMounted } from "vue";
const App = defineComponent(() => { onMounted(() => { console.log("load"); }); return () => <div>App</div>; });
export default App;
|
3. JSX 的父子通讯方式
在 SFC 中,父子之间的通讯方式主要是父组件传递 props 给子组件,子组件 emit 事件给父组件的方式进行通讯。
但是在 JSX 中,有一种 JSX 特色的通讯方式,这一点在 React 中经常使用
下面编写两个组件 Foo 和 Bar,通过点击 Bar 接受父组件的传参,并能通知父组件更变自身状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { defineComponent, ref } from "vue"; import Bar from "./Bar";
const Foo = defineComponent(() => { const fooText = ref("Foo"); const barText = ref("Bar");
return () => ( <div> {fooText.value} <Bar text={barText.value} textClick={() => { fooText.value = barText.value; }} /> </div> ); });
export default Foo;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { defineComponent } from "vue";
const Bar = defineComponent( (props) => { return () => <div onClick={props.textClick}>{props.text}</div>; }, { props: { text: String, textClick: Function, }, } );
export default Bar;
|
以上组件中,点击 Bar 中的文字,会改变 Foo 中的状态。
JSX 中其实不存在子传父,JSX 中更像子组件永远接受参数,只负责展示或者调用 props 中的数据或者函数,自己没有传递出去什么,自己只是调用了一个具有父元素作用域的函数而已。
4. 结束语
尽管 Vue 没有像 React 那样那么方便的使用 JSX,但是也在支持 JSX 的道路上走了很好的一步,而且相比 React Vue 对函数组件有着自己独特的 Hook 优势,使用函数组件能更方便的设计组件,以此记录。