Blog of CodeJELLOW’S ENGINEERING NOTES
← 返回文章

VUE3之其它组件

VUE3之其它组件 Render函数 JSX的使用 自定义指令 teleport VUE插件

Render函数

基本使用

render函数用于渲染html元素,用于利用JavaScript实现在template标签中做不了的事情。render提供一个h函数来构造元素,h函数的第一个参数是元素类型,第二个参数是元素的属性,第三个参数是元素的子组件:

TEXT
<script>
import {h} from 'vue';
  export default {
    render() {
      return h("h2",{class:"title"},"hello render");
    },
  }
</script>

render函数实现计数器

TEXT
<script>
import {h} from 'vue';
  export default {
    data() {
      return {
        counter:0
      }
    },
    render() {
      return h("div",{class:"app"},[
        h("h2",null,`当前计数:${this.counter}`),
        h("button",{
          onClick:()=>this.counter++
        },"+1"),
        h("button",{
          onClick:()=>this.counter--
        },"-1"),
      ]);
    },
  }
</script>

setup函数也可以完成替代render函数的功能:

TEXT
<script>
import {h,ref} from 'vue';
  export default {
    setup(props) {
      const counter=ref(0);
      return ()=>{
        return h("div",{class:"app"},[
        h("h2",null,`当前计数:${counter.value}`),
        h("button",{
          onClick:()=>counter.value++
        },"+1"),
        h("button",{
          onClick:()=>counter.value--
        },"-1"),
      ]);
      }
    },
  }
</script>

插槽的使用

TEXT
// HelloWorld.vue
<script>
import { h } from 'vue';
    export default {
        render() {
            return h("div",null,[
                h("h2",null,"hello world"),
                this.$slots.default? this.$slots.default({name:"sk"}):h("span",null,"我是hello world的默认插槽") 
            ]);
        },
    }
</script>
TEXT
// App.vue
<script>
import {h} from 'vue';
import HelloWorld from './HelloWorld.vue';
  export default {
    render() {
      return h(HelloWorld,{},{
        default: props=>h("span",null,`App传入HelloWorld的内容: ${props.name}`)
      });
    },
  }
</script>

JSX的使用

太怪了

实现上面的例子

TEXT
// HelloWorld.vue
<script>
    export default {
        render() {
            return(
                <div>
                    <h2>hello world</h2>
                    { this.$slots.default? this.$slots.default():<span>我是hello world的默认插槽</span>}
                </div>
            )
        },
    }
</script>
TEXT
// App.vue
<script>
import HelloWorld from './HelloWorld.vue';
    export default {
        data() {
            return {
                counter:0
            }
        },
        render() {
            const increment=()=>this.counter++;
            const decrement=()=>this.counter--;
            return (
                <div>
                    <h2>当前计数值:{this.counter}</h2>
                    <button onClick={increment}>+1</button>
                    <button onClick={decrement}>-1</button>
                    <HelloWorld>
                        {{default:props=><button>我是按钮</button>}}
                    </HelloWorld>
                </div>
            )
        },
    }
</script>

自定义指令

基本使用

局部指令如同局部组件需要在App内部使用directives option,directives是一个对象,里面定义了一些自定义的指令(缺省v-):

TEXT
<script>
    export default {
        // 局部指令
        directives:{
            focus:{
                mounted(el,bindings,vnode,preVnode) {
                    console.log("focus mounted");
                    el.focus();
                },
            }
        }
    }
</script>

全局指令如同全局组件,需要调用App的directive方法来注册:

TEXT
const app=createApp(App);

app.directive("focus",{
    mounted(el,bindings,vnode,preVnode) {
        console.log("focus mounted");
        el.focus();
    },
});
app.mount('#app')

生命周期函数

生命周期函数中传来四个参数:1.element:当前的元素;2.bindings:传入的修饰符、值等;3.vnode;4.前一个vnode:

TEXT
        directives:{
            sk:{
                created(el,bindings,vnode,preVnode) {
                    console.log("sk created",el,bindings,vnode,preVnode);
                },
                beforeMount() {
                    console.log("sk beforeMount");
                },
                mounted() {
                    console.log("sk mounted");
                },
                beforeUpdate() {
                    console.log("sk beforeUpdate");
                },
                updated() {
                    console.log("sk updated");
                },
                beforeUnmount() {
                    console.log("sk beforeUnMount");
                },
                unmounted() {
                    console.log("sk unmounted");
                },
            }
        },

对自定义指令添加修饰符和值,可以在bindings中接收到:

TEXT
<template>
    <div>
        <button v-if="counter<2" v-sk.aaa.bbbb="'123'" @click="increment">当前计数:{{counter}}</button>
    </div>
</template>
图片暂不可用。
bbibinbib

bindings使用方式:

TEXT
                created(el,bindings,vnode,preVnode) {
                    console.log("sk created",el,bindings,vnode,preVnode);
                    console.log(bindings.value);
                    console.log(bindings.modifiers);
                },
图片暂不可用。

时间格式化的练习:

TEXT
// format-time.js
import dayjs from "dayjs"

export default function(app){
    let formatString="YYYY-MM-DD HH:mm";
    app.directive("format-time",{
        created(el,bindings) {
            if (bindings.value){
                formatString=bindings.value;
            }
        },
        mounted(el,bindings) {
            const textContent = el .textContent;
            let timestamp = parseInt(textContent);
            if (textContent.length===10){
                timestamp*=1000;
            }
            el.textContent=dayjs(timestamp).format(formatString);
        },
    })
}


// App.vue
<template>
    <div>
        <h2 v-format-time="'YYYY-MM-DD'">{{timestamp}}</h2>
    </div>
</template>

通过函数注册到App中:

TEXT
// directives/index.js
import registerFormat from "./format-time";
export default function registerDirectives(app){
    registerFormat(app)
}

// main.js
registerDirectives(app);

teleport

可以内容挂载到别的标签下:

TEXT
<template>
    <div class="app">
        <teleport to='#sk'>
            <h2>当前计数</h2>
            <button>+1</button>
            <hello-world/>
        </teleport>
        <teleport to='#sk'>
            <span>呵呵呵</span>
        </teleport>
    </div>
</template>
图片暂不可用。

VUE插件

以对象方式定义一个插件,对象内部必须有install方法,并可以接收app的入参:

TEXT
export default{
    install(app){
        app.config.globalProperties.$name="shopkeeper";
    }
}

在app中使用use注册:

TEXT
app.use(pluginObject);

使用方式,created等生命周期、methods等可以获取this的方法中可以直接获取;setup中需要通过getCurrentInstance获取:

TEXT
        mounted() {
            console.log(this.$name);
        },
        setup(props) {
            const instance = getCurrentInstance();
            console.log("setup",instance.appContext.config.globalProperties.$name);
        }

以函数方式定义一个插件,就相当于定义一个install函数,并接收app入参,以函数方式定义一个注册time-format的directive的功能为例:

TEXT
// plugins/plugin_function.js
import registerFormat from "../directives/format-time";
export default function(app){
    registerFormat(app);
}
TEXT
// main.js
mport pluginFunction from './plugins/plugin_function';

const app=createApp(App);
app.use(pluginFunction);
app.mount('#app')

nextTick

nextTick类似于Android组件的post方法,即让组件更新完ui之后再执行所要求的操作:

TEXT
<template>
  <div>
    <h2 class="title" ref="titleRef">{{message}}</h2>
    <button @click="addMessageContent">添加内容</button>
  </div>
</template>

<script>
import {ref, onUpdated, nextTick} from 'vue'
  export default {
    setup(props) {
      const message = ref("");
      const titleRef =ref(null)
      const addMessageContent = ()=>{
        message.value+="哈哈哈哈哈哈";
        nextTick(()=>{
          console.log("addMessageContent",titleRef.value.offsetHeight);
        })
      };
      // 其它组件的更新也会调用这个回调
      onUpdated(() => {
        console.log("onUpdated",titleRef.value.offsetHeight);
      });
      return {
        message,
        titleRef,
        addMessageContent
      }
    }
  }
</script>

<style scoped>
  .title{
    width: 120px;
  }
</style>

通过 RSS 订阅更新 →