Vue的函数式编程

类似React,其实Vue也是支持JSX和渲染函数的,在Vue中使用这些功能可以发挥JavaScript的全部编程能力。

基本用法

Vue中提供了h函数用于创建虚拟节点并创建组件,这样的组件里面就不用写模板,后缀应该为js。和vue里的script标签一样,默认导出一个js对象:

1
2
3
4
5
import { h } from "vue";

export default function App() {
return h("h1", "Hello World");
}

然后在main.js中引入并挂载:

1
2
3
4
import { createApp } from 'vue';
import App from './App.js';

createApp(App).mount('#app');

这时页面已经可以正常显示了。

h函数在此前Vue的虚拟DOM原理中尝试实现过,是一个十分灵活的函数,接收三个参数,第一个参数是要创建的虚拟节点的标签名,第二个是虚拟节点的class、id、事件等属性,第三个是子节点。

返回渲染函数

使用渲染函数也可以使用组合式API,只要在导出的js对象中写setup函数就好了,而这时setup函数返回的内容不应该是在模板中要使用的变量,而是渲染函数:

1
2
3
4
5
6
7
import { h } from "vue";

export default {
setup() {
return () => h("h1", "Hello World");
}
}

setup中返回的渲染函数能正常访问到setup中的props、响应式变量等。

JSX

Vue中也支持使用JSX,在使用JSX之前,要做一点配置,这里使用的是vite环境:

首先要安装vite的vueJsx插件:

1
$ npm i @vitejs/plugin-vue-jsx

然后在vite.config.js中启用它:

1
2
3
4
5
6
7
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';

export default defineConfig({
plugins: [vue(), vueJsx()]
});

现在可以正常使用JSX了,上面的示例中,将App.js文件后缀改成jsx,修改文件内容为:

1
2
3
4
5
6
7
8
// App.jsx
import { h } from "vue";

export default {
setup() {
return () => <h1>Hello World</h1>;
}
}

一些例子

v-if

模板实现

1
2
3
4
5
6
7
8
9
<template>
<div v-if="ok">OK</div>
<div v-else>NO</div>
</template>

<script setup>
import { ref } from "vue";
var ok = ref(true);
</script>

JSX/渲染函数实现

1
2
3
4
5
6
export default {
setup() {
var ok = true;
return () => ok ? <h1>OK</h1> : <h1>NO</h1>;
}
}
1
2
3
4
5
6
7
8
import { h } from 'vue';

export default {
setup() {
var ok = true;
return () => ok ? h("h1", "ok") : h("h1", "no");
}
}

v-for

模板实现

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<ul>
<li v-for="item in items">
{{ item }}
</li>
</ul>
</template>

<script setup>
import { reactive } from "vue";
var items = reactive([1, 2, 3, 4, 5]);
</script>

JSX/渲染函数实现

1
2
3
4
5
6
7
8
9
export default {
setup() {
var items = [1, 2, 3, 4, 5];
return () =>
<ul>
{items.map((v) => <li>{v}</li>)}
</ul>;
}
}
1
2
3
4
5
6
export default {
setup() {
var items = [1, 2, 3, 4, 5];
return () => h("ul", items.map(v => h("li", v)));
}
}

v-on

模板实现

1
2
3
4
5
6
7
8
9
10
11
<template>
<button
@click="
() => {
console.log(1);
}
"
>
test
</button>
</template>

JSX/渲染函数实现

1
2
3
4
5
export default {
setup() {
return () => <button onClick={(v) => { console.log(v) }}>test</button>;
}
}
1
2
3
4
5
6
7
import { h } from 'vue';

export default {
setup() {
return () => h("button", { onClick: (e) => { console.log(e); } }, "test")
}
}

组件

JSX和h函数同样可以支持渲染组件:

1
2
3
4
5
6
7
8
// test.js
import { h } from 'vue';

export default {
setup() {
return () => h("h1", "Hello World");
}
}
1
2
3
4
5
6
7
8
9
// App.js
import { h } from "vue";
import test from "./components/test";

export default {
setup() {
return () => h(test);
}
}
1
2
3
4
5
6
7
8
// App.jsx
import Test from "./components/test";

export default {
setup() {
return () => <Test />;
}
}

效果是一模一样的。如果要更简单一点,也可以像下面这样:

1
2
3
4
5
import Test from "./components/test";

export default function App() {
return <Test />;
}

效果也是一模一样的。

实现计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { ref, h } from 'vue'

export default {
setup() {
var count = ref(0);
var add = () => {
console.log(count.value)
count.value++;
}
// JSX写法
return () => (
<div>
<div class="count">{count.value}</div>
<button onClick={add}>add</button>
</div>
)
// 渲染函数写法
return () => h("div", [
h("div", count.value),
h("button", { onClick: add }, "add")
])
}
}

函数式组件

函数式组件是一种定义自身没有任何状态的组件的方式。在函数式组件里定义ref、reactive等响应性的变量都是不会起作用的。函数式组件适合做那种状态不会改变的组件。例如:

1
2
3
export default function () {
return <h1>Hello World</h1>
}