vue基础

256 阅读13分钟

use( )

Vue.use( )(vue2.x 是 Vue.use,vue3.x 是 app.use)在 vue 中使用很多,比如 ElementUI,vantUI,VueI18n,Router,Vuex 等部分第三方库、组件、自定义方法等,在引用后会使用 Vue.use 将他们传入作为参数使用,将他们注册到 Vue。

vue2.x 使用 Vue.use( )

import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store';
// 引入国际化插件
import VueI18n from 'vue-i18n';
// 引入统一配置
import common from './lib/common';
// 引入animate.css
import animated from 'animate.css';

Vue.use(animated);
Vue.use(VueI18n); // 通过插件的形式挂载
Vue.use(common); // 全局配置项

/* eslint-disable no-new */
// 把 vue实例挂载在 window.vm,方便使用 vue的实例
window.vm = new Vue({
    el: '#app',
    router,
    store,
    components: {
        App,
    },
    template: '<App/>',
});

vue3.x 使用 app.use( )

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import vant from 'vant';
import 'vant/lib/index.css';
import globalFunction from './utils/globalFunction'; // 引入全局方法

const app = createApp(App);

app.use(vant);
app.use(store);
app.use(router);

// 挂载全局方法
for (const key in globalFunction) {
    app.provide(key, globalFunction[key]);
    console.log(key);
}

app.mount('#app');

window.vm = app;

拓展

注册全局组件/方法

在 vue2 中

vue2.x 利用 Vue.prototype 注册全局方法/变量/组件。

有些时候我们需要在 vue 项目的任意位置都能调用公共方法/变量,那么 vue 如何注册全局的方法/变量/组件呢?请看以下示例:

1.创建 common.js(编写插件),插件内利用 Vue.prototype 全局挂载方法

import { Toast } from 'vant';
import echarts from 'echarts';

const install = (Vue) => {
    // toast提示, 通过 this.Toast调用
    Vue.prototype.Toast = Toast;
    // echarts, 通过 this.$echarts调用
    Vue.prototype.$echarts = echarts;
    // 获取 url 参数
    Vue.prototype.$getUrlParam = (name, href) => {
        const _search = href ? href.split('?')[1] : window.location.search.substr(1);
        if (_search) {
            const _reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i');
            const _regNext = _search.match(_reg);
            if (_regNext) {
                return decodeURI(_regNext[2]) || '';
            }
            return '';
        }
        return '';
    };
};

2.main.js(通过Vue.use注册刚刚编写的自定义插件)

import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store';

// 引入自定义插件
import common from './lib/common';
Vue.use(common); // 全局配置项

/* eslint-disable no-new */
// 把 vue实例挂载在 window.vm,方便使用 vue的实例
window.vm = new Vue({
    el: '#app',
    router,
    store,
    components: {
        App,
    },
    template: '<App/>',
});

3.在任意组件中使用刚刚通过 Vue.prototype 全局注册的方法,比如使用 $getUrlParam 方法,直接通过 this.$getUrlParam 即可调用。

<template>
    <div class="page">
        Vue.prototype全局注册的方法测试
    </div>
</template>

<script>
export default {
    name: 'Test',
    data() {},

    created() {
        console.log(this.$getUrlParam('idCard'))
    },
};
</script>

4.在任意js文件中使用刚刚通过Vue.prototype全局注册的方法,比如使用 $getUrlParam 方法。因为我们已经在 main.js 中将 vue 实例挂载在 window.vm,所以在 js 文件中,直接通过 window.vm.$getUrlParam 即可调用。

console.log(window.vm.$getUrlParam('idCard'))

当然,如果不想通过编写组件然后Vue.use的方式引入全局方法,也可以直接在main.js通过Vue.prototype注册全局方法/变量/组件。

// mian.js:
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store';
	
// 通过Vue.prototype注册全局方法/变量/组件
// toast提示,通过this.Toast调用
Vue.prototype.Toast = Toast;
// echarts,通过this.$echarts调用
Vue.prototype.$echarts = echarts;
// 获取url参数
Vue.prototype.$getUrlParam = (name, href) => {
    const _search = href ? href.split('?')[1] : window.location.search.substr(1);
    if (_search) {
        const _reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i');
        const _regNext = _search.match(_reg);
        if (_regNext) {
            return decodeURI(_regNext[2]) || '';
        }
        return '';
    }
    return '';
};
	
// 把 vue实例挂载在 window.vm,方便使用 vue的实例
window.vm = new Vue({
    el: '#app',
    router,
    store,
    components: {
        App,
    },
    template: '<App/>',
});

需要注意的是,只有vue2.x支持Vue.prototype注册全局方法/变量/组件,vue3.x不再支持Vue.prototype。

拓展

在 vue3 中

vue2.x 利用 Vue.prototype 注册全局组件/方法,然而 vue3.x 是无法通过Vue.prototype 来注册全局组件/方法的,因此需要另辟蹊径。

vue3.x注册全局组件/方法主要有以下两个方式:

1.provide / inject (推荐)
main.js中:通过 provide 将组件或者方法、变量挂载在全局。

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';

const app = createApp(App);

app.use(store);
app.use(router);

// 挂载全局方法
const globalFunc = () => {
    console.log('要挂载在全局的方法');
};
// 将 globalFunc方法挂载在全局
app.provide('globalFunc', globalFunc);

app.mount('#app');

// 把 vue实例挂载在 window.vm,方便使用 vue的实例
window.vm = app;

组件中:通过 inject 获取全局方法并调用

<template>
    <div>组件中通过inject获取全局方法并调用</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, inject } from 'vue';
export default defineComponent({
    name: 'JobDetail',
    // 注册组件
    components: {
        Header,
    },
    setup() {
        onMounted(() => {
            testFunc();
        });

        const testFunc = (): void => {
            // 通过 inject 获取挂载在全局的 globalFunc方法
            const globalFunc: any = inject('globalFunc'); 
            globalFunc();
        };

        return {
            testFunc,
        };
    },
});
</script>

拓展

路由

在 vue3 中

hash路由:

import { createRouter, createWebHashHistory } from 'vue-router';

const routes = [
  {
    path: '/',
    component: () => import('@/views'),
  },
  {
    path: '/test1',
    name: 'test1',
    component: () => import('@/views/test1'),
  },
  {
    path: '/test2',
    name: 'test2',
    component: () => import('@/views/test2'),
  },
];

export const router = createRouter({
    history: createWebHashHistory(),
    routes: routes,
});

// 访问页面:
http://192.168.1.3:8080/#/test1

history路由:

import { createRouter, createWebHistory} from 'vue-router';

const routes = [
  {
    path: '/',
    component: () => import('@/views'),
  },
  {
    path: '/test1',
    name: 'test1',
    component: () => import('@/views/test1'),
  },
  {
    path: '/test2',
    name: 'test2',
    component: () => import('@/views/test2'),
  },
];

export const router = createRouter({
    history: createWebHistory(),
    routes: routes,
});

// 访问页面:
http://192.168.1.3:8080/test1

Class 与 Style 绑定

对象语法

<template>
    <div 
        class="static" 
        v-bind:class="{ active: isActive }"></div> // <div class="static active"></div>
</template>
<script>
export default {
    data() {
        return {
            isActive: true
        }
    }
}
</script>
<template>
    <div v-bind:class="classObject"></div> // <div class="active"></div>
</template>
<script>
export default {
    data: {
        isActive: true,
        error: null
    },
    computed: {
        classObject: function () {
            return {
                active: this.isActive && !this.error,
                'text-danger': this.error && this.error.type === 'fatal'
            }
        }
    }
}
</script>

数组语法

<template>
    <div 
        :class="[ isActive ? activeClass : '', errorClass]"></div> // <div class="active text-danger"></div>
</template>
<script>
export default {
    data: {
        isActive: true, 
        activeClass: 'active',
        errorClass: 'text-danger'
    }
}
</script>
<template>
    <div 
        :class="[{ safe: isSafe }, activeClass, errorClass]"></div> // <div class="safe active text-danger"></div>
</template>
<script>
export default {
    data: {
        isSafe: true, 
        activeClass: 'active',
        errorClass: 'text-danger'
    }
}
</script>

内联样式

<template>
    <div 
        :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> // <div style="color:red;font-size:30px"></div>
</template>
<script>
export default {
    data: {
        activeColor: 'red',
        fontSize: 30
    }
}
</script>
<template>
    <div 
        :style="styleObject"></div> // <div style="color:red;font-size:30px"></div>
</template>
<script>
export default {
    data: {
        styleObject: {
            color: 'red',
            fontSize: 30
        }
    }
}
</script>

jsx

① 插槽;

// 父组件:
render() {
  {/* 具名插槽  */}
  <myComponent>
    <header slot="header">header</header>
    <header slot="content">content</header>
    <footer slot="footer">footer</footer>
  </myComponent>
}

// 子组件:
render() {
  return (
    <div>
      {/* 纯文本 */}
      <p>我是自定义组件</p>
      {this.$slots.header}
      {this.$slots.content}
      {this.$slots.footer}
    </div>
  );
}
// 父组件:
render() {
  {/* 具名插槽 作用域插槽 */}
  <myComponent {
    ...{
      scopedSlots: {
        test: ({user}) => (
          <div>{user.name}</div>
        )
      }
    }
  }>
  </myComponent>
}

// 子组件:
render() {
  return (
    <div>
      {this.$scopedSlots.test({
        user: this.user
      })}
    </div>
  );
}
// content.js
export default {
    name: 'content',
    functional: true,
    props: {
        render: Function,
        column: {
            type: Object,
            default: null
        },
        params: {
            type: Object,
            default: () => {}
        }
    },
    render: (h, ctx) => {
        const params = ctx.props.params
        return ctx.props.render && ctx.props.render(h, params)
    }
}
<el-tabs
    v-model="activeName">
    <el-tab-pane
        v-for="item in tabList"
        :key="item.code"
        :label="item.label">
        <span v-if="item.render">
            <content
                :params="item.params"
                :render="item.render"></content>
        </span>
    </el-tab-pane>
</el-tabs>
<script>
import Content from './content'
components: {
    Content
},
</script>

② v-model;

// 在vue中
<el-input
    v-model="searchParams.searchVal">
</el-input>

// 对应jsx语法 
function _input (value) {
    this.searchParams.searchVal = value
},
render(h, params) => {
    // 这里也可以从params传入参数
    return (
        <el-input
            value={this.searchParams.searchVal}
            onInput={_input}>
        </el-input>
    )
}

③ v-html;

render(h, params) { 
    return ( 
        <div> <div domPropsInnerHTML={htmlStr}></div> </div> 
    )
}

④ v-if;

// 在vue中
<el-button v-if="show"></el-button>

// 对应jsx语法
render(h, params) {
    return (
        this.show && <el-button></el-button>
    )
}

拓展
如何在vue中使用jsx语法
Vue插槽用法,在JSX中的用法
在vue中使用jsx语法
Vue 中使用 JSX
Vue+JSX 使用例子
Vue2 + JSX使用总结
vue2.x 使用JSX 开发
vue2.0如何实现jsx的组件功能
vue中的渲染函数/jsx和插槽slot
使用Vue 2和JSX绑定自定义事件
在vue中如何使用jsx,并使用v-model等自定义指令
为什么 JSX 语法这么香?

自定义指令

// directives.js:
Vue.directive("changePageSize", {
  bind(el, binding) {
    setPageSizeData(binding);
  },
  update(vnode, binding) {
    // window.onresize = function () {
    //   setPageSizeData(binding);
    // };
  },
});

const setPageSizeData = (binding) => {
  console.log(binding);
  let page = 0;
  if (window.innerHeight < 800) {
    page = 10;
  } else if (window.innerHeight >= 800 && window.innerHeight < 1200) {
    page = 20;
  } else {
    page = 30;
  }
  binding.value.setPageSize(page);
};

// main.js:
import "./utils/directives";

// demo.vue:
<template>
    <el-pagination
      v-changePageSize="{ setPageSize: handleSizeChange }"
      @size-change="handleSizeChange"
      background
      @current-change="handleCurrentChange"
      :current-page="this.pageData.currentPage"
      :page-sizes="[15, 30, 45, 60, 85, 100]"
      :page-size="this.pageData.pageSize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="this.pageData.total"
    ></el-pagination>
</template>
<script>
export default {
    data(){
        return {
            pageData: {
                pageSize: 10,
            }
        }
    },
    methods: {
        handleSizeChange(val) {
            this.pageData.pageSize = val;
        }
    }
}
</script>

拓展
vue中如何自定义指令
vue2 自定义指令中怎么改变data中的值
vue 改变 data 值,自定义指令

v-model 指令

<input v-model="text" />

上例不过是一个语法糖,展开来是:

<input :value="text" @Input="e => text = e.target.value" />

拓展
Vue 实现双向绑定的几种方法

.sync 修饰符

.sync 的作用:实现父子组件数据之间的双向绑定, 与 v-model 类似;
区别:在 vue2 中一个组件上只能有一个 v-model,而 .sync 修饰符可以有很多个。

原理
v-model 的原理

<com1 v-model="num"></com1>
// 等价于
<com1 :value="num" @input="(val)=>this.num=val"></com1>

.sync修饰符的原理

// 正常父传子: 
<com1 :a="num" :b="num2"></com1>
 
// 加上sync之后父传子: 
<com1 :a.sync="num" .b.sync="num2"></com1> 
 
// 它等价于
<com1 
  :a="num" @update:a="val=>num=val"
  :b="num2" @update:b="val=>num2=val"></com1> 
 
// 相当于多了一个事件监听,事件名是update:a,回调函数中,会把接收到的值赋值给属性绑定的数据项中。

image.png

小结
.sync 与 v-model 区别是
① 相同点: 都是语法糖, 都是可以实现父子组件中的数据的双向通信;
② 区别点:
格式不同:
v-model="num" , :num.sync="num"
v-model : @input + value
:num.sync: @update:num
v-model 只能用一次; .sync 可以有多个

拓展
修饰符.sync (Vue2)
vue2 自定义组件—v-model—.sync修饰符—具名插槽—作用域插槽—混入
Vue中.sync的用法
vue2中v-model .sync 和 vue3中v-model的使用
vue2.0中.sync与v-model
Vue 双向绑定语法糖 .sync 和 v-model

插件开发与使用

Vue的插件就是能够给 vue 添加新功能新特性的东西。

插件通常会为 Vue 添加全局功能。

插件能实现的功能没有限制,不过常见以下几种:
① 通过插件添加全局方法或属性;
② 通过插件添加全局资源:指令、过滤器、过渡等;
③ 通过插件(使用全局 mixin 方法),添加一些组件选项;
④ 通过插件,添加 vue 实例方法,通过把它们添加到 Vue.prototype 上实现;
⑤ 一个库,提供自己的 api,同时提供上面提到的一个或多个功能,如 vue-router。

开发插件
所谓 vue 插件其实就是一个简单的 js 对象而已,然后这个插件对象有一个公开属性 install ,他的值为一个函数,此函数接受两个参数。第一个参数是 Vue 构造器 , 第二个参数是一个可选的选项对象。
最后导出此插件对象以供别人使用。

// src/utils/myPlugin.js:
const myPlug = {
    install: (Vue, opts={}) => {
        // 插件要实现的功能;
    }
}
export default myPlug;

// src/main.js:
import Vue from 'vue';
import App from './App';
import myPlug from './utils/myPlugin.js';
Vue.use(myPlug); // 使用插件 myPlug;

插件案例

// src/utils/myPlugin.js:
import MessageBox from 'element-ui/lib/message-box';
import Loading from 'element-ui/lib/loading';

const myPlug = {
    install: (Vue, opts={}) => {
        Vue.prototype.$testProp = 'Hello World';
        Vue.prototype.$testMethod = (msg) => {
            alert('我是插件:myPlug,你的信息为:' + msg);
        };
        Vue.prototype.$loading = Loading.service;
        Vue.prototype.$msgbox = MessageBox;
    }
}
export default myPlug;

拓展
vue之插件开发及使用
Vue.js 插件开发详解
编写自定义Vue.js插件

选项式 API 与组合式 API

选项式 API

// vue2 中写法:
<template>
  <div class="wrapper">
    <p>{{ obj.a }}</p>
    <p>{{ obj.b }}</p>
    <p>{{ obj.c }}</p>
    <p>{{ obj.d }}</p>
    <button @click="handleA">修改数据 a</button>
    <button @click="handleB">修改数据 b</button>
    <button @click="handleC">添加数据 c</button>
    <button @click="handleD">添加数据 d</button>
  </div>
</template>

<script>
export default {
  name: 'demo',
  data() {
    return {
      obj: {
        a: '原始数据 a',
        b: '原始数据 b'
      }
    };
  },
  mounted() {
    console.log(this);
  },
  methods: {
    handleA() {
      this.obj.a = '数据 a 改变了';
      console.log(this);
    },
    handleB() {
      this.$set(this.obj, 'b', '数据 b 改变了');
      console.log(this);
    },
    handleC() {
      this.obj.c = '数据 c 添加失败';
      console.log(this);
    },
    handleD() {
      this.$set(this.obj, 'd', '数据 d 添加成功');
      console.log(this);
    }
  }
};
</script>

修改数据前: 1655033420101.png 修改数据 a 后: 1655033746540.png 修改数据 b 后: 1655033981826.png 添加数据 c 后: 1655034205977.png 添加数据 d 后: 1655034374053.png

组合式 API

setup

// vue3 中写法:
<template>
  <div class="wrapper">首页</div>
  <p>{{ obj.a }}</p>
  <p>{{ obj.b }}</p>
  <p>{{ obj.c }}</p>
  <button @click="handleData">修改数据</button>
</template>

<script>
export default {
  setup() {
    let obj = {
      a: 1,
      b: 2,
    };
    const handleData = () => {
      obj.c = 3;
      console.log(obj);
    };
    return {
      obj,
      handleData,
    };
  },
};
</script>

修改数据后: 1655035578582.png

reactive 通常定义复杂类型的数据,定义后的数据具有响应式特性。

// reactive:
<template>
  <div class="wrapper">首页</div>
  <p>{{ obj.a }}</p>
  <p>{{ obj.b }}</p>
  <p>{{ obj.c }}</p>
  <button @click="handleData">修改数据</button>
</template>

<script>
import { reactive } from '@vue/reactivity';
export default {
  setup() {
    let obj = reactive({
      a: 1,
      b: 2,
    });
    const handleData = () => {
      console.log(obj);
      obj.c = 3;
      console.log(obj);
    };
    return {
      obj,
      handleData,
    };
  },
};
</script>

修改数据后: 1655036128305.png

ref 通常定义简单类型的数据,当然也可以定义复杂类型的数据,定义后的数据具有响应式特性。

// ref:
<template>
  <div class="wrapper">首页</div>
  <p>{{ obj }}</p>
  <button @click="handleData">修改数据</button>
</template>

<script>
import { ref } from '@vue/reactivity';
export default {
  setup() {
    let obj = ref({
      a: 1,
      b: 2,
    });
    console.log(obj);
    const handleData = () => {
      obj.value.c = 3;
      console.log(obj);
    };
    return {
      obj,
      handleData,
    };
  },
};
</script>

修改数据前: 1655037287551.png 修改数据后: 1655037604234.png

vue3 中 data(),setup(),onMounted(),mounted()的执行顺序为:setup(), data(),onMounted(), mounted()。

<template>
  <div class="wrapper"></div>
</template>

<script>
import { onMounted } from '@vue/runtime-core';
export default {
  data() {
    console.log('data');
    return {};
  },
  setup() {
    console.log('setup');
    onMounted(() => {
      console.log('onMounted');
    });
    return {};
  },
  mounted() {
    console.log('mounted');
  },
};
</script>

// 依次打印: setup, data, onMounted, mounted

setup 语法糖写法(推荐该写法)

// setup 语法糖写法:
<template>
  <div class="wrapper">
    <p>{{ msg }}</p> 
    <p>{{ obj }}</p>
    <button @click="handleData">修改数据 msg</button>
    <button @click="handleData2">修改数据 obj</button>
  </div>
</template>

<script setup>
const { onMounted, ref, reactive } = require('@vue/runtime-core');

let msg = ref('这是响应式数据');
let obj = reactive({ a: 1, b: 2 });
const handleData = () => {
  msg.value = '修改响应式数据成功';
};
const handleData2 = () => {
  obj.a = 3;
};
onMounted(() => {
  console.log('onMounted');
});
</script>

toRefs 可以完成数据的解构,如果数据具有响应性,可以保持响应性的特性。

// 选项式 API 写法:
<template>
  <div class="wrapper">
    <p>{{ name }}</p>
    <p>{{ age }}</p>
    <button @click="handleData">修改数据</button>
  </div>
</template>

<script>
const { reactive, toRefs } = require('@vue/reactivity');
export default {
  name: 'demo',
  setup() {
    let obj = reactive({
      name: '张三',
      age: 18,
    });

    const handleData = () => {
      obj.name = '张三修改了';
    };
    return {
      ...toRefs(obj),
      handleData,
    };
  },
};
</script>
// 组合式 API 写法:
<template>
  <div class="wrapper">
    <p>{{ name }}</p>
    <p>{{ age }}</p>
    <button @click="handleData">修改数据</button>
  </div>
</template>

<script setup>
const { reactive, toRefs } = require('@vue/reactivity');

let obj = reactive({
  name: '张三',
  age: 18,
});
const { name, age } = toRefs(obj);
const handleData = () => {
  console.log(name);
  name.value = '张三修改了';
};
</script>

1655042486202.png

setup 语法糖插件

① setup 语法糖插件:unplugin-auto-import; 解决场景:② 在组建中开发无需每次都引入 const { onMounted, ref, reactive } = require('@vue/runtime-core')。

下载安装:npm i unplugin-auto-import -D
配置:

computed 计算属性

在 vue2 中

  • 定义:要用的属性不存在,要通过已有属性计算得来。
  • 原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
  • get函数什么时候执行?
    (1).初次读取时会执行一次。
    (2).当依赖的数据发生改变时会被再次调用。
  • 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
  • 备注:
    (1).计算属性最终会出现在vm上,直接读取使用即可。(data的属性是在vm的_data上)
    (2).如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

在使用计算属性的时候,大部分的使用场景只读取不修改,因此可以简写。

// 接受一个 getter 函数:
data() {
    return {
        firstName: 'Foo',
        lastName: 'Bar'
    }
},
computed: {
    // 简写
    fullName: function() { // 该 function 是 getter 函数
        return this.firstName + '-' + this.lastName;
    }
}
// 接受一个具有 getter 和 setter 函数的对象:
data() {
    return {
        firstName: 'Foo',
        lastName: 'Bar'
    }
},
computed: {
    // 完整写法
    fullName: {
        // getter
        get() {
            return this.firstName + '-' + this.lastName;
        }
        // setter
        set(newVal) {
            const arr = newVal.split('-'); 
            this.firstName = arr[0]; 
            this.lastName = arr[1];
        }
    }
}
// 带参数的计算属性
<template>
    <p>{{ getSum(20) }}</p>
</template>
<script>
expoert default {
    data() {
        return {
            x: 10,
        }
    },
    computed: {
        getSum() {
            return (y) => {
                return this.x + y;
            }
        }
    }
}
</script>
// 计算属性的第一个参数指向该 vue实例
<template>
    <p>{{ getX }}</p>
</template>
<script>
expoert default {
    data() {
        return {
            x: 10,
        }
    },
    computed: {
        getX(app) { // app 指向当前上下文的 this(即当前组件实例)
            return app.x;
        }
    }
}
</script>

在 vue3 中

computed() 用来创建计算属性,computed() 函数的返回值是一个 ref 的实例。使用 computed 之前需要按需导入:import { computed } from 'vue'

用法:① 接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象(即该 ref 对象只读不可写);② 接受一个具有 get 和 set 函数的对象,用来创建可写的 ref 对象。

拓展

// 接受一个 getter 函数:
const count = ref(1) // 创建一个 ref 响应式数据
const plusOne = computed(
    // getter
    () => count.value + 1 // 参数为 getter 函数,我们只能读取值,不能修改值
) // 不可变的 ref 对象

console.log(plusOne.value) // 2 (触发 getter 函数)

plusOne.value++ // 错误
// 接受一个具有 getter 和 setter 函数的对象:
const count = ref(1)
const plusOne = computed({
    // getter
    get: () => count.value + 1,
    // setter
    set: val => {
        count.value = val - 1
    }
}) // 可变的 ref 对象

plusOne.value = 1 // 触发 setter 函数
console.log(count.value) // 0 // 触发 getter 函数
// 写法一:
<template>
  <div class="wrapper">
    <p>{{ changeName }}</p>
  </div>
</template>

<script setup>
const { reactive } = require('@vue/reactivity');
const { computed } = require('@vue/runtime-core');

let obj = reactive({
  name: '张三',
});
const changeName = computed(() => {
  return obj.name.slice(0, 1);
}); // 不可变的 ref 对象
</script>

// 写法二(高级写法):
<template>
  <div class="wrapper">
    <p>{{ changeName }}</p>
  </div>
</template>

<script setup>
const { reactive } = require('@vue/reactivity');
const { computed } = require('@vue/runtime-core');

let obj = reactive({
  name: '张三',
});
const changeName = computed({
  get() {
    return obj.name.slice(0, 1);
  },
  set() {},
});
</script>

// 写法三(不常见):
<template>
  <div class="wrapper">
    <p>{{ obj.str }}</p>
  </div>
</template>

<script setup>
const { reactive } = require('@vue/reactivity');
const { computed } = require('@vue/runtime-core');

let obj = reactive({
  name: '张三',
  str: computed(() => {
    return obj.name.slice(0, 1);
  }),
});
</script>

watch 监听

vue 中 watch 用来监听数据的响应式变化,获取数据变化前后的值。

在 vue2 中

监听属性:
1.当被监视的属性变化时,回调函数自动调用,进行相关操作
2.监视的属性必须存在,才能进行监视!!(在data里或者computed里)
3.深度监视:
(1).Vue中的watch默认不监测对象内部值的改变(一层)。
(2).配置deep:true可以监测对象内部值改变(多层)。

备注:
(1).Vue 自身可以监测对象内部值的改变,但 Vue 提供的 watch 默认不可以!
(2).使用 watch 时根据数据的具体结构,决定是否采用深度监视。

通常需要对基本数据、复杂数据进行监听,故 watch 有四种情况:① 普通的 watch;② 数组的 watch;③ 对象的 watch;④ 对象中某个属性的 watch。

// 普通的 watch:
data: {
    meter: 1000,
    kilameter: 1
},
watch: {
    meter: function(newVal, oldVal) {
        this.kilameter = newVal * 0.1;
    },
    kilameter: function(newVal, oldVal) {
        this.meter = newVal *1000;
    }
}

// 数组的 watch:
data: {
    arr: [1,2,3]
},
watch: {
    arr: function(newVal, oldVal) {
        console.log(newVal);
        console.log(oldVal);     
    }
}

// 对象的 watch:
data: {
    obj: {
        a:111,
        b:222
    }  
},
watch: {
    obj: {
        handler: function(newVal, oldVal) {
            console.log(oldVal);
        },
        immediate: true, // 初始化时让 handler调用一下
        deep: true
    }
}

// 对象中某个属性的 watch:
data: {
    obj: {
        a: 111,
        b: 222
    }  
},
watch: {
    'obj.a': {
        handler: function(newVal, oldVal) {
            console.log(oldVal);
        }
    }
}

在 vue3 中

vue3的监听跟vue2有点不一样,使用 watch 之前需先按需导入:import { watch } from "vue";,然后直接在setup里面使用,调用方式是以回调函数的方式呈现。

语法:
① watch(监听的数据,副作用函数,配置对象);
② watch(data, (newData, oldData) => {}, {immediate: true, deep: true});
③ immediate:true,初次加载 watch 侦听器立即被执行;deep:true,深度监听。

拓展

// 选项式写法:
<template>
  <div class="wrapper">
    <input type="text" v-model="msg" />
  </div>
</template>

<script>
const { ref } = require('@vue/reactivity');
const { watch } = require('@vue/runtime-core');

export default {
  setup() {
    let msg = ref('这是一个数据');
    watch(
      msg,
      (newVal, oldVal) => {
        console.log(newVal, oldVal);
      },
      {
        immediate: true, // 初始化监听
      }
    );
    return {
      msg,
    };
  },
};
</script>

// 初始化监听:
<template>
  <div class="wrapper">
    <input type="text" v-model="msg" />
  </div>
</template>

<script setup>
const { ref } = require('@vue/reactivity');
const { watch } = require('@vue/runtime-core');

let msg = ref('这是一个数据');
watch(
  msg,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  {
    immediate: true, // 初始化监听
  }
);
</script>

// 监听多个数据:
<template>
  <div class="wrapper">
    <input type="text" v-model="msg" />
    <input type="text" v-model="str" />
  </div>
</template>

<script setup>
const { ref } = require('@vue/reactivity');
const { watch } = require('@vue/runtime-core');

let msg = ref('这是一个数据');
let str = ref('这是一个字符串');
watch(
  [msg, str], // 同时监听多个
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  {
    immediate: true,
  }
);
</script>

// 监听对象
<template>
  <div class="wrapper">
    <input type="text" v-model="obj.a" />
    <input type="text" v-model="obj.arr" />
  </div>
</template>

<script setup>
const { reactive } = require('@vue/reactivity');
const { watch } = require('@vue/runtime-core');

let obj = reactive({
  a: 1,
  arr: ['a', 'b', 'c'],
});
watch(obj, (newVal, oldVal) => {
  console.log(newVal, oldVal);
});
</script>

// 监听对象中某个对象
<template>
  <div class="wrapper">
    <input type="text" v-model="obj.a" />
    <input type="text" v-model="obj.arr" />
  </div>
</template>

<script setup>
const { reactive } = require('@vue/reactivity');
const { watch } = require('@vue/runtime-core');

let obj = reactive({
  a: 1,
  arr: ['a', 'b', 'c'],
});
watch(
  () => obj.arr, // 监听对象中某个对象(此处不能写成 obj.arr,否则无效)
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  }
);
</script>

// watchEffect 立即执行监听函数:
<template>
  <div class="wrapper">
    <input type="text" v-model="name" />
    <input type="text" v-model="obj.a" />
    <input type="text" v-model="obj.arr" />
  </div>
</template>

<script setup>
const { ref, reactive } = require('@vue/reactivity');
const { watchEffect } = require('@vue/runtime-core');

let name = ref('张三');
let obj = reactive({
  a: 1,
  arr: ['a', 'b', 'c'],
});
watchEffect(() => {
  console.log(name.value); // name 的值变化就会监听到
  console.log(obj.a); // obj.a 的值变化就会监听到
  console.log(obj.arr); // obj.arr 的值变化就会监听到
});
</script>

插槽

插槽就是子组件中的提供给父组件使用的一个占位符,用<slot></slot> 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>标签,父组件填充的内容称为插槽内容

  • 子组件不提供插槽时,父组件填充失效
  • 父组件无填充时,<slot></slot>中的备用内容会启用生效
  • 父级模板里的所有内容都是在父级作用域中编译的,子模板里的所有内容都是在子作用域中编译的,互不影响。

匿名插槽

又名默认插槽

// parent.vue:
<template>
    <Child>提交按钮</Child>
</template>

<script setup>
import Child from './child.vue';
</script>

// child.vue:
<template>
    <button>
        <slot>SUBMIT BTN</slot>
    </button>
</template>

// parent 组件渲染为:
<button>提交按钮</button>

具名插槽

当有多个插槽时,插槽增加了name属性来正确渲染对应的部分,父组件需要使用<template></template>。可以认为匿名插槽是特殊的具名插槽

// parent.vue:
<template>
    <Child>
        匿名插槽的内容
        <!-- <template v-slot:default>匿名插槽的内容</template> -->
        <!-- <p>匿名插槽的内容</p> -->
        <!-- 上面三种方式效果一样 -->

        <!-- 填充内容顺序无关 -->
        <template #footer>自定义 footer</template><!-- #footer 是 v-slot:footer 的简写 -->
        <template v-slot:header>自定义 header</template>
    </Child>
</template>

<script setup>
import Child from './child.vue';
</script>

// child.vue:
<template>
    <header>
        <slot name="header"></slot>
    </header>
    <main>
        <slot></slot><!-- 其实就是: <slot name="default"></slot> -->
    </main>
    <footer>
        <slot name="footer"></slot>
    </footer>
</template>

// parent 组件渲染为:
<header>自定义 header</header>
<main>"匿名插槽的内容"</main>
<footer>自定义 footer</footer>

作用域插槽

让父级插槽内容能够访问子组件数据,数据从子组件往父组件流动。子组件通过插槽prop(任意个数)来绑定数据,父组件通过带值(命名随意)的v-slot来获取子组件的数据。

// parent.vue:
<template>
  <Child>
    <template v-slot:default="slotProps">
      <p>{{ slotProps }}</p>
    </template>
  </Child>
</template>

// 写法二: 只有默认插槽时, v-slot可以写在组件上
//<template>
//  <Child v-slot:default="slotProps">
//    <p>{{ slotProps }}</p>
//  </Child>
//</template>

// 写法三: v-slot有解构的用法
//<template>
//  <C v-slot:default="{ data, index }">
//    <p>{{ { data, index } }}</p>
//  </C>
//</template>

<script setup>
import Child from './child.vue';
</script>

// child.vue:
<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">
      <!-- data 即插槽 prop, 绑定 item, index 也是插槽 prop, 绑定 index,
          备用内容为 item.thing1 -->
      <slot :data="item" :index="index">{{ item.thing1 }}</slot>
    </li>
  </ul>
</template>

<script setup>
const items = [
  { thing1: '阅读', thing2: '看电视' },
  { thing1: '背单词', thing2: '玩游戏' },
];
</script>

// parent 组件渲染为:
<ul>
    <li>
        <p>{ "data": { "thing1": "阅读", "thing2": "看电视" }, "index": 0 }</p>
    </li>
    <li>
        <p>{ "data": { "thing1": "背单词", "thing2": "玩游戏" }, "index": 1 }</p>
    </li>
</ul>

动态数据写入插槽

// parent.vue:
<template>
  <div class="wrapper">
    {{ f }}
    <Child>
      <template #[f]>
        <p>我来自 parent</p>
      </template>
    </Child>
  </div>
</template>

<script setup>
import { ref } from '@vue/reactivity';
import Child from './child.vue';
const f = ref('footer');
</script>

// child.vue:
<template>
  <div class="header">
    <slot name="header">这是 header</slot>
  </div>
  <div class="footer">
    <slot name="footer">这是 footer</slot>
  </div>
</template>

<script setup></script>

// parent 组件渲染为:
<div class="wrapper">
    "footer"
    <div class="header">这是 header</div>
    <div class="footer">我来自 parent</div>
</div>

拓展
el-tooltip内容用插槽展示(slot#content用法)

Teleport 传送

组件间数据进行传递,写法有:class 类、id 、标签。
<teleport to=".main"></teleport>
<teleport to="#cont"></teleport>
<teleport to="body"></teleport>

注意:teleport 必须传递到有这个 dom 的元素中,所以 dom 元素必须在 teleport 的前面,否则无效。

// index.vue:
<template>
  <div class="wrapper">
    <div class="accept"></div>
  </div>
  <div id="cont"></div>
  <Child></Child>
</template>

<script setup>
import Child from './child-view.vue';
</script>

// child-view.vue:
<template>
  <div class="child_wrapper">
    <p>子组件</p>
    <teleport to=".accept">
      <p>这是传送</p>
    </teleport>
    <teleport to="#cont">
      <p>这是传送的 cont</p>
    </teleport>
  </div>
</template>

1655047378852.png

// index.vue:
<template>
  <div class="wrapper">
    <div class="accept"></div>
  </div>
  <teleport to=".accept">
    <p>这是传送</p>
  </teleport>
</template>

<script setup></script>

1655047491063(1).png

动态组件

语法:<component :is="动态去切换的组件"></component>

<template>
  <div class="wrapper">
    <ul>
      <li v-for="(item, index) in tabList" :key="index" @click="changeTab(index)">{{ item.name }}</li>
    </ul>
    <keep-alive>
      <component :is="currentComp.comp"></component>
    </keep-alive>
  </div>
</template>

<script setup>
import { markRaw, reactive } from '@vue/reactivity';
import A from './a-view.vue';
import B from './b-view.vue';
let tabList = reactive([
  { name: 'tabA', comp: markRaw(A) }, // markRaw() 包裹组件,组件就不会响应,从而提高性能
  { name: 'tabB', comp: markRaw(B) },
]);
let currentComp = reactive({
  comp: tabList[0].comp,
});
const changeTab = (index) => {
  currentComp.comp = tabList[index].comp;
};
</script>

异步组件

异步组件主要进行性能提升。

使用场景:① 组件按需引入:当满足条件后再去加载该组件;② 打包分包处理:npm run build 打包完成后,异步组件有单独的 js 文件,是从主体 js 分包出来的。

<template>
  <div class="wrapper">
    <C v-if="loadBool"></C>
    <button @click="handleLoad">加载组件</button>
  </div>
</template>

<script setup>
const { defineAsyncComponent, ref } = require('@vue/runtime-core');

const C = defineAsyncComponent(() => import('./c-view.vue'));
let loadBool = ref(false);
const handleLoad = () => {
  loadBool.value = true;
};
</script>

组件递归(自调用)

递归:无限套娃,当找到某个娃娃时,就不套了。

image.png

// tree.vue:
<template>
  <ul>
    <li v-for="(item, index) in data" :key="index">
      {{ item.name }}
      <!-- 在遍历时递归调用组件自身,当然,要有children,有数据的时候才去递归调用自身(递归需要有一个结束条件) -->
      <template v-if="item.children">
        <tree :data="item.children"></tree>
        <!-- 因为组件调用渲染,需要有数据,而这个tree.vue组件是的数据是通过props接收的,
        所以需要把子内容数据再传递给子组件,子组件用props接收,就能够一层一层的渲染了 -->
      </template>
    </li>
  </ul>
</template>

<script>
export default {
  name: "tree",
  props: {
    data: {
      type: Array,
      default() {
        return [];
      },
    },
  },
};
</script>

拓展:
vue组件的递归自调用~代码思路分析
vue 组件自调用
Vue组件自我调用,实现递归展示
vue 菜单递归
vue之菜单的递归渲染思想

vue实现点击页面其他地方,隐藏div元素

mounted(){
    document.addEventListener('click', (e) => {
        let that = this;
        if(!this.$el.contains(e.target)){
            that.videoShow = false; // 点击其他区域关闭;
        } else {
            that.videoShow = true; 
        }
    })
}
/**
 * 关闭搜索下拉列表;
 */
handleCloseSearchPage() {
    document.addEventListener('click', (e) => {
        const searchPageBox = this.$refs.searchPageBox;
        if (!searchPageBox.contains(e.target)) {
            this.showSearchPage = false;
            this.keyVal = null;
        }
    });
},