# render渲染函数和$mount挂载

render: h => h(App) 是下面内容的缩写:

render: function (createElement) {
    return createElement(App);
}

进一步缩写为(ES6 语法):

render (h){
    return h(App);
}

Vue.js 里面的 createElement 函数,这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实 DOM 节点,并挂载到根节点上。

还有另外一种写法效果是一样的:

import App from './App'
new Vue({
    el: '#root',
    template: '<App></App>',
    components: {
        App
    }
})

# vue createElement函数

createElement 接受的参数:

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签名、组件选项对象,或者
  // resolve 了上述任何一种的一个 async 函数。必填项。
  'div',

  // {Object}
  // 一个与模板中 attribute 对应的数据对象。可选。
  {
    // (详情见下一节)
  },

  // {String | Array}
  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可以使用字符串来生成“文本虚拟节点”。可选。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)
  • 参数可以省略,这是他可能的取值
{
  // 与 `v-bind:class` 的 API 相同,
  // 接受一个字符串、对象或字符串和对象组成的数组
  'class': {
    foo: true,
    bar: false
  },
  // 与 `v-bind:style` 的 API 相同,
  // 接受一个字符串、对象,或对象组成的数组
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // 普通的 HTML attribute
  attrs: {
    id: 'foo'
  },
  // 组件 prop
  props: {
    myProp: 'bar'
  },
  // DOM property
  domProps: {
    innerHTML: 'baz'
  },
  // 事件监听器在 `on` 内,
  // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
  // 需要在处理函数中手动检查 keyCode。
  on: {
    click: this.clickHandler
  },
  // 仅用于组件,用于监听原生事件,而不是组件内部使用
  // `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  // 赋值,因为 Vue 已经自动为你进行了同步。
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // 作用域插槽的格式为
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果组件是其它组件的子组件,需为插槽指定名称
  slot: 'name-of-slot',
  // 其它特殊顶层 property
  key: 'myKey',
  ref: 'myRef',
  // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  // 那么 `$refs.myRef` 会变成一个数组。
  refInFor: true
}
Vue.component("myview",{
	render(h){
		return h('div',{attrs:{id:"kk"}},['测试1',h("a",["ssss"])])
	}
})
  • 渲染成以下dom
<div id="kk">测试1<a>ssss</a></div>
  • h函数甚至可以传的是一个组件自身
let test=Vue.component("test",{
	render(h){
		return h("span",'-------------------')
	}
})

Vue.component("myview",{
	render(h){
		return h('div',{attrs:{id:"kk"}},['测试1',h("a",["ssss"]),h(test,{style:{color:'red'}},["ssss1"])])
	}
})
<div id="kk">
	测试1
	<a>ssss</a>
	<span style="color: red;">-------------------</span>
</div>

# vue用render实现v-model功能操作

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Render函数实现v-model</title>
</head>
<body>
<div id="app">
    <ele></ele>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
	// 创建ele组件
    Vue.component('ele', {
        render: function(createElement) {    // render函数
            return createElement('div', [
                createElement('input', {
                    domProps: {
                        value: this.value  // 类似于使用v-bind将data选项中的value变量绑定到input的value属性上
                    },
                    on: {
                        input:(event)=> {
							console.log(this)
                            this.value = event.target.value;
                        }
                    }
                }),
                createElement('p', 'value: ' + this.value),
                // 创建button按钮用来测试
                createElement('button', {
                    attrs: {
                        type: 'button'
                    },
                    domProps: {
                        innerHTML: 'button'
                    },
                    on: {
                        click: this.handleClick
                    }
                })
            ])
        },
        data: function () {
            return {
                value: ''
            }
        },
        methods: {
            handleClick: function () {
                this.value = "按下button时会变成我"
            }
        }
    });

    var app = new Vue({
        el: '#app'
    });
</script>
</body>
</html>

# vue渲染

从模板到真实dom节点还需要经过一些步骤

  1. 把模板编译为render函数
  2. 实例进行挂载, 根据根节点render函数的调用,递归的生成虚拟dom
  3. 对比虚拟dom,渲染到真实dom
  4. 组件内部data发生变化,组件和子组件引用data作为props重新调用render函数,生成虚拟dom, 返回到步骤3

mainjs

import Vue from 'vue'
import App from './App'

console.log(App)

new Vue({
  render: h => h(App)
}).$mount('#app')

# vue虚拟节点VNode

把Vue的实例挂载到#app, 会调用实例里面的render方法,生成虚拟DOM

new Vue({
  render: h => {
    let root = h(App)
    console.log('root:', root)
    return root
  }
}).$mount('#app')
VNode {tag: "vue-component-5-app", data: {…}, children: undefined, text: undefined, elm: undefined, …}

VNode就是虚拟节点,虚拟节点里面有一个属性elm, 这个属性指向真实的DOM节点。因为VNode指向了真实的DOM节点,那么虚拟节点经过对比后,生成的DOM节点就可以直接进行替换。

一个组件对象,如果内部的data发生变化,触发了render函数,重新生成了VNode节点。那么就可以直接找到所对应的节点,然后直接替换。那么这个过程只会在本组件内发生,不会影响其他的组件。于是组件与组件是隔离的。

// main.js
const root = new Vue({
  data: {
    state: true
  },
  mounted() {
    setTimeout(() => {
      console.log(this)
      this.state = false
    }, 1000)
  },
  render: function(h) {
    const { state } = this // state 变化重新触发render
    let root = h(App)
    console.log('root:', root)
    return root
  }
}).$mount('#app')

// App.vue
<script>
export default {
  render: (h) => {
    let app = h('h1', ['hello world'])
    console.log('app:', app)
    return app
  }
}
</script>

可以看到,当main.js中重新触发render函数的时候,render方法里面有引用App.vue这个子组件。但是并没有触发App.vue组件的的render函数。

# new Vue的实际应用

create函数用于动态创建指定组件实例并挂载至body

import Vue from "vue";
// 创建函数接收要创建组件定义
function create(Component, props){
// 创建一个Vue新实例
	const vm = new Vue({
		render(h) {
		// render函数将传入组件配置对象转换为虚拟dom
			console.log(h(Component, { props }));
			return h(Component, { props });
		}
	}).$mount(); //执行挂载函数,但未指定挂载目标,表示只执行初始化工作
	// 将生成dom元素追加至body
	document.body.appendChild(vm.$el);
	// 给组件实例添加销毁方法
	const comp = vm.$children[0];
	comp.remove = () => {
		document.body.removeChild(vm.$el);
		vm.$destroy();
	};
	return comp;
}
// 暴露调用接口
export default create;

创建通知组件,Notice.vue

<template>
  <div id="tk">
    <h3  v-if="isShow">{{title}}</h3>
    <p>{{message}}</p>
  </div>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: ""
    },
    message: {
      type: String,
      default: ""
    },
    duration: {
      type: Number,
      default: ""
    }
  },
  data() {
    return {
      isShow: false
    };
  },
  methods: {
    show() {
      this.isShow = true;
      setTimeout(() => {
          this.hide()
      }, this.duration);
    },
    hide() {
      this.isShow = false;
      this.remove();
    }
  }
};
</script>

<style lang="scss" scoped>
</style>

测试

<script>

import Notice from "../Notice";
import create from "@/utils/create";

export default {
 
  methods: {
    onLogin() {
      // 创建弹窗实例
      let notice;

      this.$refs.loginForm.validate(isValid => {
        notice = create( Notice,{
          title: "xxx1",
          message: isValid ? "登录!!!" : "有错!!!",
          duration: 10000
        });

        notice.show();
      });
    }
  }
};
</script>

# render函数代替template的写法

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>lesson 31</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="root"></div>
</body>
<script>
  // render function
  // template -> render -> h -> 虚拟DOM(JS对象)-> 真实 DOM -> 展示到页面上
  const app = Vue.createApp({
    template: `
      <my-title :level="2">
        hello dell
      </my-title>
    `
  });

  app.component('my-title', {
    props: ['level'],
    render() {
      const { h } = Vue;
	  console.log(h)
      return h('h' + this.level, {}, [
        this.$slots.default(),
        h('h4', {}, 'dell')
      ])
    }
  })

  const vm = app.mount('#root');
</script>
</html>
最后更新: 8/1/2023, 9:43:25 PM