Vue 组价挂载的常用方式
常规挂载的弊端
- 组件的模板是通过调用接口从服务度获取的,组件需要动态渲染;
- 组建挂载的位置在入口组件外,如body层,此时就得用其他挂载方式了;
Vue.extend 与 $mount简介
Vue.extend
Vue.extend其实就是类的继承的意思,是一种寄生组合式的继承,其作用就是基于Vue构造器,创建一个子类,参数和new Vue的基本一样,data要和内部组件一样,是一个函数,再配合$mount就可以让组件进行渲染,并挂载到任意指定节点上;
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
// 一个类肯定可以创建多个实例,而在vue的內部它是直接将传入的option配置对象的data作为数据来源,即状态。为了保持每个Profile实例组件都独立,就得用函数返回一个对象进行独立化
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
var profile1 = new Profile().$mount('#mount-point')
var profile2 = new Profile().$mount('#mount-point')
var profile3 = new Profile().$mount('#mount-point')
方式二
import CustomControlBar from '@/componeents/customControlBar.vue'
// CustomControlBar 内部逻辑和常规的Vue组件定义方式一致
const placeholder = document.querySelector('.xgplayer-controls')
const ControlBar = Vue.extend(CustomControlBar)
const Component = new ControlBar({
propsData: {
giftShow: this.giftShow,
danmuShow: this.danmuShow,
eventBus: this.eventBus,
isLogin: this.isLogin,
},
}).$mount()
placeholder.appendChild(Component.$el)
普通的 new 实例化时传入el配置
- 如果new Vue时候的option的el属性存在,那么它会自动的绑定并渲染到页面上
new Vue({
el: '#app',
// el: document.getElementById('app')
template: `<div id="app">Lbxin{{ message }}</div>`,
data () {
return {
message: "_11"
};
}
})
// main.js 方式挂载
import Vue from 'vue';
import App from './app.vue';
new Vue({
el: '#app',
render: h => h(App)
});
new 实例化时不传 el 配置,创建完后手动挂载
var vm = new Vue({ //没有el选项
template: `<div id="page-container">Lbxin</div>`
})
// 没有传el,模板将被初始化渲染为DOM之外的元素
vm.$mount('#app') //根据mount挂载的配置进行绑定渲染
new 实例化和创建完后手动挂载都不传 el 配置
var vm = new Vue({ //没有el选项
template: `<div id="page-container">Lbxin</div>`
})
vm.$mount() //没有传递el配置 渲染在内存中 此时可以通过vm.$el进行指定节点的打印 但DOM中不存在
document.body.appendChild(vm.$el) //通过将其打入DOM中进行绑定渲染
$mount 快捷挂载方式
// 在 $mount ⾥写参数来指定挂载的节点
new AlertComponent().$mount('#app');
// 不⽤ $mount,直接在创建实例时指定 el 选项
new AlertComponent({ el: '#app' });
配合render函数进行渲染后指定挂载
在Vue中是使用模板HTML语法组建页面的,使用render函数就可以用js语言来构建页面DOM;Vue是虚拟DOM,在拿到template模板时也要转义成VNode的函数,当使用render构建DOM时也就免去了Vue的转义过程
import Vue from "vue";
import toast from "./toast.vue";
const props = {
message: "账号被封禁,请联系客服人员",
icon: "forbid",
errno: 80002
};
const ToastTem = new Vue({
render(h) {
return h(toast, {
props,
});
},
});
const component = ToastTem.$mount();
document.body.appendChild(component.$el);
// 获取组件实例
const toastExam = ToastTem.$children[0]
// render返回多个DOM组件
// render: (h, params) => {
// var arr = params.row.policyFile.split(";");
// return h(
// "ul",
// arr.map(function (item, index) {
// return h(
// "a",
// {
// domProps: {
// href: item,
// target: "_black",
// },
// style: {
// marginRight: "5px",
// },
// },
// "文件" + parseInt(index + 1)
// );
// })
// );
// };
渲染挂载后进行销毁
采用
$mount手动渲染的组件,如果要进行销毁操作,需要进行DOM级别的removeChild和Vue提供的$destroy进行手动销毁实例,最好将组件的实例进行赋值为null
export default {
methods: {
destroyCode() {
const $target = document.getElementById(this.id);
if ($target) $target.parentNode.removeChild($target);
if (this.component) {
this.$refs.display.removeChild(this.component.$el);
this.component.$destroy();
this.component = null;
}
},
},
beforeDestroy() {
this.destroyCom();
},
};
Vue3兼容
Vue的构建方式有两种,独立构建(standalone)和运行时构建(runtime-only),但在Vue3中只有运行时,不允许编译template模板,所以在Vue3中需要单独配置或者转化,通常为配置化进行结局
// vue.config.js
module.exports = {
runtimeCompiler: true
};
// 此配置会导致应用额外增加10kb左右
render 渲染函数与 function render
在vue2中使用了Virtual DOM来更新DOM组件,提升渲染性能;在常规的vue组件开发中,模板都是写在template中,但在真正编译阶段会被解析为 Virtual DOM(基于JavaScript计算的,开销相对较小);
// Virtual DOM
const vNode = {
tag: "div",
attributes: {
id: "app",
},
children: [
// ......
],
};
h函数 - Render函数的核心
- h函数即createElement,是Render的核心,其包括三个参数,分别是
- 要渲染的元素或组件,可以是一个HTML标签、组件或者一个函数,次配置为必填
// 1. html 标签 h("div"); // 2. 组件选项 import Top from "../component/top.vue"; h(Top);- 组件属性的数据对象,如组件的常规样式、id、props、事件、自定义指令等
官方配置文档
h("button", { onClick: () => this.counter++, class: "button" }, "加 1"),- 子节点,类型是String或Array,同样是一个h函数
[ "头部组件", h("p", "注册"), h(Component, { props: { userInfo: {}, }, }), ];
虚拟节点是组件或含有组件的slot - 循环和工厂函数
在h函数中,如果vNode是组件或者含有组件的slot,那么vNode必须是唯一的,如需要重复渲染同一个组件元素,可以通过一个循环和工厂函数来解决; 通过循环和工厂函数的使用,将原本相同的组件都克隆一份,包含其中的关键属性的复制
函数组件和插槽
子组件的渲染有时候需要由父组件进行动态DOM或组件相应的传入,这时就需要用到插槽来实现;
// 父组件
<script>
import { h, ref } from "vue";
import Test from "./components/Test.vue"
export default {
setup() {
return {
age: 123
}
},
render() {
return h(Test, null, {
// default 对应的是一个函数,default是默认插槽
default: props => h("span", null, "父组件动态内容传递:" + props.name + "age: " + props.age||this.age)
})
}
}
</script>
<script>
// 子组件
import { h } from "vue";
export default {
render() {
return h("div", null, [
h("div", null, "我是子组件"),
/**
* 可以使用自定义插槽或者默认的default插槽进行展示
* 也可以传入一个参数,使用的是 函数传参
*/
this.$slots.default ? this.$slots.default({ name: "Lbxin" }) : h("div", null, "我是子组件的默认值")
])
}
}
</script>
@vue/babel-plugin-jsx
使用 @vue/babel-plugin-jsx 在Render中使用jsx语法进行编写,通过配置Babel进行语法转义,便于开发;
// babel.config.js
module.exports = {
presets: ["@/vue/cli-plugin-babel/preset"],
plugins: ["@vue/babel-plugin-jsx"],
};
// 使用
render() {
return (
<div>
<div>JSX的使用</div>
<h2>当前数字:{this.counter}</h2>
</div>
)
}
Render的使用场景分析
- 需要用到多个相同的slot,常规的就是采用命名插槽进行解决,当然也可以使用深度克隆的方法进行解决
- 服务端渲染的方式进行渲染界面
- 运行时版本的vue中,不允许使用template进行编译渲染
- 具有复杂结构或样式时(可以用v-html进行渲染,但是只能解析常规的HTML字符串且存在XSS的分险)、动态的组件渲染中,普通的slot等方法是无法实现的,比如复杂table中的动态表单功能,一种是使用render,另一种是使用多个作用域插槽实现
Functional Render 函数化组件渲染
Vue.js 提供了⼀个 functional 的布尔值选项,设置为 true 可以使组件⽆状态和⽆实例,也就是没有
data和 this 上下⽂。这样⽤Render 函数返回虚拟节点可以更容易渲染,因为函数化组件(Functional Render)只是⼀个函数,渲染开销要⼩很多。
函数式组件的优点
- 渲染开销很小(通过原生js进行渲染,本质上只是函数)
- 没有状态,不用像vue的响应式需要经过额外的初始化操作
- 便于包装组件
- 程序化的在多个组件中选择一个来代为渲染
- 在将children、props、data传递给子组件前操作它们
函数式组件的特点
- 没有状态
- 没有响应式数据 - 不用管理任何状态
- 也不监听任何传递给他的状态
- 没有实例
- 没有this上下文
- 事件只能通过父组件传递
on: { click: context.listeners.click }, - 没有生命周期函数
- 只接受props参数
可以利用函数式组件的特性,将其做成高阶组件(High order components),即可以生成其他组件的组件
Functional Render 没有上下文一说,主要用于中转一个组件
函数式组件的适用场景分析
- 目标是一个简单的展示组件,也就是所谓的dumb组件爱你,如tags、text、静态页面等
- 高阶组件,用于接受一个组件作为参数,返回一个被包裹过的组件
- v-for中每项通常都是很好的候选项
函数化组件
- 使用函数化组件,render函数提供了第二个参数context来提供临时上下文。通过该参数来实现组件间的数据传递,如data、props、slots等;
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
return createElement(){
'button',
on: {
click: context.listeners.click
},
// ...
}
}
})
- 函数化组件的使用场景
- 程序化的在多个组件中选择一个
- 在将children、props、data传递给子组件之前进行处理
- 可以定义一个基类的中间文件,进行传统组件的创建逻辑复用;
export default {
functional: true,
props: {
render: Function,
},
render: (h, ctx) => {
return ctx.props.render(h);
},
};
<!-- my-component.vue -->
<template>
<div>
<Render :render="render"></Render>
</div>
</template>
<script>
import Render from './render.js';
export default {
components: { Render },
props: {
render: Function
}
}
</script>
<!-- demo.vue -->
<template>
<div>
<my-component :render="render"></my-component>
</div>
</template>
<script>
import myComponent from '../components/my-component.vue';
export default {
components: { myComponent },
data() {
return {
render: (h) => {
return h('div', {
style: {
color: 'red'
}
}, '⾃定义内容');
}
}
}
}
</script>
案例浅析
<div id="app">
<top :topInfo="topInfo" />
</div>
<script>
Vue.component('top', {
functional: true, // 开启函数组件配置
props: {
topInfo: {
type: Array,
required: true
},
size: {
required: true,
validator(value) {
return oneOf(value, ["small", "large", "mini"]);
},
default: "small"
}
},
render(createElement, context) {
let userName = createElement(
"div",
{
style: {
width: "450px",
height: "30px",
color: "blue"
}
},
context.props.listData[0].userName
);
let userButton = createElement(
"button",
{
style: {
size: context.props.size,
border: "1px solid #f5f6f7"
}
},
context.props.listData[0].button
)
let userAge = createElement(
"div",
{
style: {
width: "450px",
height: "30px",
color: "#ccc"
}
},
context.props.listData[0].userAge // content is passed down by context
);
return createElement(
"div",
{
style: {
width: "1000px",
height: "300px"
}
},
[userName, userButton, userAge]
)
}
})
const app = new Vue({
data() {
return {
topInfo: [
{
userName: "Lbxin",
userAge: 22,
button: "logOut"
},
{
shopName: "官方店铺",
shopCount: 10
}
]
}
}
}).$mount("#app");
</script>