返回文章

初识 Vue3

cli 创建项目时可直接选择安装 sass loader 、 vue router 、 Vuex , vite 则需项目创建完成后手动配置 此外 vite 提供了对 .scss , ....

项目创建

Vue2

  • VueCli

    $ vue create hello-world

Vue3

  • Vite

    $ npm init vite@latest

cli创建项目时可直接选择安装sass-loadervue-routerVuexvite则需项目创建完成后手动配置

此外vite提供了对 .scss, .sass, .less, .styl.stylus 文件的内置支持,无需添加响应loader只需添加响应预处理器依赖即可

# .scss and .sass
$ npm install -D sass

# .less
$ npm install -D less

项目基础配置

打包配置

Vue2

vue.config.js

Vue3

vite.config.js

环境变量配置

全局环境变量均配置于项目.env文件中,此处区别于cli中自定义变量需以VUE_APP_为前缀,参考 cli 环境变量vite中以VITE_为前缀定义,参考 vite 环境变量

使用方式也存在区别,cli 中使用process.env.VUE_APP_***方式获取变量值;vite中使用import.meta.env.VITE_***

生命周期

Vue2

Vue2生命周期

Vue3

Vue3生命周期

选项式/组合式

Vue2中的写法即选项式Api方式

<script>
export default {
  name: 'Test',
  data () {
    return {
      message: 'TestMessage',
    }
  },
  methods: {
    handleClick () {
      console.log(this.message);
    },
  }
}
</script>

选项式Api即导出为一个对象,内部通过配置各选项实现功能,例namedatamethods等选项。

Vue3中推荐使用的写法为组合式写法

<script setup>
const message = ref('TestMessage');
const handleClick = () => {
  console.log(message.value);
}
</script>

总结

选项式Api:

优点:由于各选项职责明确所以更易于学习和使用

缺点:代码组织性相对较差,相似的逻辑代码不便于复用且逻辑复杂度较高时代码可读性会降低

组合式Api:

优点:功能逻辑复杂时,各个功能逻辑代码组织在一起,便于阅读和维护

缺点:对于编写代码者本身有一定的代码组织能力的要求,需要有良好的代码组织能力和拆分逻辑能力

组合式 API 的出现是为Vue新增赋能,并不是为了取代选项式 API,故而Vue3中仍可使用选项式代码风格进行编写

响应式数据

<template>
<span>{{obj.a}}</span>
</template>
<script>
export default {
  data () {
    return {
      obj: {
        a: ''
      },
    };
  },
  methods: {
    appendParam () {
      this.$set(this.obj, 'b', 2);
    },
  }
}
</script>
<template>
	<span @click="objRef.b = 1">{{objRef.b}}</span>
</template>
<script setup>
  const obj = reactive({a: 1});
  const appendParam = () => {
    obj.b = 2;
    console.log(obj.b)
  };
  const setObj = () => {
    Object.assign(obj, {c: 3});
  };
  const cleanObj = () => {
    Object.keys(obj).forEach(key => delete obj[key]);
  };

  const objRef = ref({});
  const setObjRef = () => {
    objRef.value = {a: 1, b: 2};
  };
  const cleanObjRef = () => {
    objRef.value = {};
    console.log(obj.value.b)
  };
  
  class ObjModel = {
    a = 'a'
    b = 'b'
  }
  const objModel = reactive(new ObjModel())
  
  const setObjModel = () => {
    objModel.c = 'c'
  }
  const resetObjModel = () => {
    Object.assign(objModel, new ObjModel());
  }

  const arr = reactive([]);
  const changeArr = () => {
    arr.length = 0;
    arr.push(...[1, 2, 3]);
  };
</script>

组件

Vue 2

<script>
export default {
  name: 'TestComponent',
  model: {
    prop: 'value',
    event: 'change'
  },
  props: {
    value: {
      type: Boolean,
      default: false
    },
    message: {
      type: String,
      default: 'default-message'
    }
  },
  methods: {
    handleClick () {
      this.$emit('customClick', 'text click')
      this.$emit('change', !this.value);
    }
  }
}
</script>

Vue 3

<script setup>
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false,
  },
  message: {
    type: String,
    default: 'default-message'
  }
});
const emits = defineEmits(['customClick', 'update:modelValue'])
const handleClick = () => {
  emits('customClick', 'text click')
  emits('update:modelValue', !props.modelValue)
}
const exposeFun = () => {
  console.log("I'm public function")
}
defineExpose({
  exposeFun,
});
</script>

组件 Ref 区别

<template>
	<input ref='input'/>
</template>
<script>
export default {
  mounted() {
    this.$refs.input.focus();
  }
}
</script>
<template>
	<input ref='input'/>
</template>
<script setup>
const input = ref(null)
const fun = () => {
  input.value?.focus()
}
onMounted(() => {
  input.value.focus()
})
</script>

路由

基础用法

<script>
export default {
  created () {
    console.log(this.$route, this.$router)
  },
}
</script>
<script setup>
const route = useRoute();
const router = useRouter();
</script>

KeepAlive区别

<template>
  <transition>
    <keep-alive>
      <router-view v-if="!$route.meta.keepAlive"></router-view>
    </keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
  </transition>
</template>
<template>
	<router-view v-slot="{ Component, route }">
    <keep-alive :include="cachedViews">
      <component :is="Component" :key="route.path" />
  	</keep-alive>
  </router-view>
</template>

全局状态管理

Vuex

基本使用

import Vuex from 'vuex'

Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: 'todo item done', done: true },
      { id: 2, text: 'todo item not done', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  },
  mutations: {
    addTodo (state, todo) {
      state.todos.push(todo);
    }
  },
  actions: {
    addTodo ({state, commit}, todo) {
      commit('addTodo', todo)
    }
  },
  modules: {}
})

Pinia

基本使用

import { createPinia } from 'pinia'

app.use(createPinia())
import { defineStore } from 'pinia'
export const useTodosStore = defineStore('todos', {
  state: () => ({
    todos: [
      { id: 1, text: 'todo item done', done: true },
      { id: 2, text: 'todo item not done', done: false }
    ]
  }),
  getters: {
    doneTodos (state) {
      return state.todos.filter(todo => todo.done)
    },
  },
  actions: {
    addTodo(text) {
      this.todos.push({ text, id: 3, done: false })
    },
  }
})
<script setup>
  import { useTodosStore } from '@/stores/todos'
  const todoStore = useTodosStore();
  console.log(todoStore.doneTodos);
  // 更新数据
  todoStore.todos.push({ text: '外部添加', id: 4, done: false });
  todoStore.todos = [{ text: '外部添加', id: 4, done: false }];
  // 或
  todoStore.$patch({ todos: [] });
  // 或
  todoStore.addTodo({ text: '外部添加', id: 4, done: false });

</script>

计算属性

<script>
export default {
  data () {
    return {
      count: 1,
    };
  },
  computed: {
    doubleCount () {
      return this.count * 2;
    }
  }
}
</script>
<script setup>
  const count = ref(1);
	const doubleCount = computed(() => count.value * 2);
</script>

侦听器

<script>
export default {
  data () {
    return {
      count: 1,
      countObj: {
        count: 1,
      }
    };
  },
  watch: {
    count (val) {
      console.log('watch - count', val);
    },
    'countObj.count' (val) {
      console.log('watch - countObj.count', val);
    },
  }
}
</script>
<script setup>
  const count = ref(1);
  const countObj = reactive({count: 1});
  watch(count, (val) => {
    console.log('watch - count', val);
  });
  watch(() => countObj.count, (val) => {
    console.log('watch - countObj.count', val);
  });
</script>

可复用性

混入

// src/mixins/logMessage.js
export default {
    methods: {
        logMessage (message) {
            console.log(message)
        },
    }
};
<script>
import LogMessage from '@/mixins/logMessage';
export default {
  mixins: [LogMessage],
  mounted () {
    this.logMessage('mounted')
  }
}
</script>

Vue3中虽保留了对于mixin的支持但也仅仅是为了兼容老版本及各依赖库,对于 Vue3 来说还是建议用 组合式函数 来实现复用部分代码。

// logMessage.js
export function useMessage() {
  const logMessage = (message) => {
    console.log(message)
  };
  return { logMessage }
}
<script setup>
import { useMessage } from './logMessage.js'

const { logMessage } = useMessage();
onMounted(() => {
  logMessage('mounted')
});
</script>

自定义指令

export default {
  bind (el, binding, vnode) {},
  inserted () {},
  update () {},
  componentUpdated () {},
  unbind () {},
}
export default {
  created(el, binding, vnode, prevVnode) {},
  beforeMount() {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {},
  unmounted() {}
}

过滤器