确保你的系统已安装了最新版本的 Vue CLI。如果未安装,请使用以下命令进行全局安装:
npm install -g @vue/cli

创建vue3项目

vue create vue3-learn

创建vue3项目的时候可能会出现 Cannot find module ‘vue-loader-v16/package.json

解决办法 :

① npm install npm@latest -g(更新 npm)

② npm i vue-loader-v16 (vue-Loader 是一个用于加载解析 Vue 单文件组件的 webpack 加载器)

package.json 在dependencies配置项中显示,我们当前使用的版本为3.1.4

自定义创建Vue3+ts项目:

node版本(14.x以上)、Vue-cli版本(4.x以上)

vue create 项目名--------选择自定义创建项目

光标到对应行,然后按空格即可选中需要的配置

 

vue3项目main.ts文件发生的变化

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

核心代码:createApp(App).mount('#app') = createApp(根组件).mount('public/index.html中的div容器')

vue3.0中按需导出了一个createApp

全局定义属性值: 

案例全局定义axios:

vue2写法:

import Vue from 'vue'
import App from './App.vue'
import axios from '@/utils/request.js'
Vue.prototype.$axios = axios
new Vue({
  render: h => h(App),
}).$mount('#app')

vue3写法:

globalProperties官方提供的在全局挂载方法和属性,代替了Vue2中的Vue.prototype 

import { createApp } from 'vue'
import App from './App.vue'
import axios from '@/plugins/axiosInstance.js'
const app = createApp(App) 
app.mount('#app') //全局挂载到#app元素上
app.config.globalProperties.$axios = axios; //配置axios的全局引用

vue3中不再强制要求必须有根元素

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

Vue 组合式 API 生命周期钩子:

在 Vue 3 中,setup() 函数是一个新的组件选项,它替代了 Vue 2.x 中的 beforeCreatecreated 生命周期钩子函数。它是使用 Composition API 来编写 Vue 组件的入口点。

setup() 函数是一个普通的 JavaScript 函数,它接收两个参数:propscontext

  • props 是一个响应式对象,包含组件接收的属性。您可以在 setup() 函数中直接使用它们。

  • context 是一个包含了一些常用的上下文对象和方法的对象,如 emitattrsslots 等。

Vue2 Options-based APIVue Composition API
beforeCreate         createdsetup()                                      替代了 beforeCreatecreated
beforeMountonBeforeMount                                      组件挂载到 DOM 之前执行
mountedonMounted                                            组件挂载到 DOM 后执行
beforeUpdate             onBeforeUpdate                                    组件更新之前执行
updatedonUpdated                                            组件更新完成后执行
beforeDestroyonBeforeUnmount                                 组件卸载之前执行
destroyedonUnmounted                                       组件卸载后执行
errorCapturedonErrorCaptured                                   全局错误捕获钩子函数

使用案例:

import { onBeforeMount, onMounted } from 'vue';
export default {
  setup() {
    onBeforeMount(() => {
      console.log('component beforeMount!');
    })
    onMounted(() => {
      console.log('component mounted!');
    })
  }
};

在Vue 3中,有许多函数可用于处理组件逻辑、状态管理和视图渲染等方面。以下是一些Vue 3中常用的函数:

  1. reactive:将一个普通对象转换为响应式对象。
  2. ref:将一个值包装为一个响应式引用。
  3. computed:创建一个计算属性,根据其他响应式对象的值计算得出。
  4. watch:监视数据的变化,当数据发生改变时执行相应的回调函数。
  5. onMounted:在组件被挂载到 DOM 后执行的生命周期钩子函数。
  6. onUpdated:在组件更新完成后执行的生命周期钩子函数。
  7. onUnmounted:在组件被卸载前执行的生命周期钩子函数。
  8. toRefs:将响应式对象转换为普通的响应式引用。
  9. inject:从祖先组件的 provide 中获取注入的值。
  10. provide:在父组件中提供值,以便在子组件中使用 inject 进行注入。

ref 将一个值包装为一个响应式引用

返回值是一个对象,且只包含一个 .value 属性。

在 setup() 函数内,由 ref() 创建的响应式数据返回的是对象,所以需要用 .value 来访问。

import { ref, onMounted } from "vue";
export default {
  setup() {
    // 定义简单数据
    const num = ref(1233);
    // 定义复杂数据
    const num2 = ref({ a: "我是num2" });
    onMounted(() => {
      console.log(num2.value); //Proxy {a: '我是num2'}
    });
    return { num, num2 };
  }
};

reactive 将一个普通对象转换为响应式对象

reactive就是用来完善ref的获取时直接省去.value,只能定义复杂数据类型的数据

import { reactive, onMounted } from "vue";
export default {
  setup() {
    // 定义对象
    const numobj = reactive({
      name: "vue3",
      slogan: "学的不仅是技术,更是梦想!"
    });
    // 定义数组
    const numlist = reactive([
      { text: "Google" },
      { text: "Runoob" },
      { text: "Taobao" }
    ]);
    onMounted(() => {
      console.log(numlist);//Proxy {0: {…}, 1: {…}, 2: {…}}
    });
    return { numobj, numlist };
  }
};

toRefs 将响应式对象转换为普通的响应式引用

toRefs函数是Vue 3中提供的一个辅助函数,用于将响应式对象reactive转换为普通的响应式引用ref

import { ref, reactive, toRefs } from 'vue';
const state = reactive({
  name: 'Alice',
  age: 25
});

const stateRefs = toRefs(state);
console.log(stateRefs.name.value); // 输出:"Alice"

定义方法:

const handleOk  = () => {}
function handleOk() {}

vue3中Ant Design (Antd) 双向绑定数据:

Ant Design Vue — An enterprise-class UI components based on Ant Design and Vue.jsAn enterprise-class UI components based on Ant Design and Vuehttps://www.antdv.com/components/input-cn/

Antd 没有像 Vue.js 默认的 v-model 语法糖来实现双向绑定。相反,Antd 提供了自己的 API 来处理双向绑定。

通常,Antd 的表单组件有一个 value 属性用于接收外部传入的值,并通过一个 onChange 事件来触发值的修改。你需要手动将传入的值赋给组件的 value 属性,并在适当的时机调用 onChange 事件来更新数据。

v-model:value="form.username"
const form = reactive({
  username: undefined,
})
@change="check"

const check = (e) => {
  console.log(e)
}

vue3中element-plus双向绑定数据: 

Input 输入框 | Element Plusa Vue 3 based component library for designers and developershttps://element-plus.org/zh-CN/component/input.html

Element Plus 的表单组件都支持 v-model,包括输入框、选择器、开关等。通过在组件上使用 v-model 指令,可以将组件的值与外部的数据进行绑定

 <el-input v-model="inputValue"></el-input>

vue3编写组件的几种方式:

1.选项式写法: 定义的数据函数等需要return出去才会生效

<template>
  <div>{{num3}}</div>
</template>

<script>
import { ref } from "vue";

export default {
  data() {},
  methods: {},
  mounted() {},
  setup() {
    const num3 = ref(1);

    return { num3 };
  },
};
</script>

2.组合式写法: setup vue3的语法糖,不需要return出去

<template>
  <div>{{num}}</div>
</template>
<script setup>
import { ref } from "vue";
const num = ref(123);
</script>

3.JSX写法:使用defineComponent来书写

<template>
  <div>
    <div> {{ count }}</div>
    <button @click="addcount">点击加一</button>
  </div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
  data() {
    return { count: 1 };
  },
  methods: {
    addcount() {
      this.count++;
    }
  }
});
</script>

vue3中使用$refs访问组件或 DOM 元素的引用

在 Vue 3 中,getCurrentInstance 是一个全局函数,用于获取当前正在执行的组件实例。

通过使用 getCurrentInstance,我们可以在组件内部的任何地方访问和操作当前组件实例的属性和方法。

import {ref,onMounted,nextTick, getCurrentInstance,} from 'vue'

let currentInstance = ''
onMounted(() => {
  currentInstance = getCurrentInstance()
})

const addaboutisif = ref(false)
const addsyse = (type) => {
  if (type == 'add') {
    addaboutisif.value = true
    nextTick(() => {
      currentInstance.refs.aboutref.init(type)
    })
  }
}

在 Vue 3 中,defineExpose 是一个组件选项,用于公开组件内部的方法或属性给父组件访问。

通过使用 defineExpose,开发人员可以明确地指定哪些组件内部的方法和属性应该对外可见。这样,父组件就可以直接访问子组件内部的这些公开的内容,而不需要通过 ref 或其他方式来间接获取。

import { defineExpose } from 'vue'

const init = (type) => {
   console.log(type)
  }
}

defineExpose({ init })

Vue3 自定义组件

在 Vue 3 中,defineComponent 是一个创建组件的辅助函数。

通过使用 defineComponent,我们可以定义一个组件,并传入配置选项来描述组件的行为、模板和其他属性

组件可以扩展 HTML 元素,封装可重用的代码。

<script setup>
import { defineComponent, h, ref } from 'vue'
import App from '../App.vue'
const MyUnoob = defineComponent({
  name: 'MyUnoob',
  setup() {
    const count = ref(0)
    const handleClick = () => {
      console.log('按钮被点击了!')
      count.value++
    }
    return () =>
      h(
        'button',
        {
          onClick: handleClick,
        },
        `自定义组件${count.value}`,
      )
  },
})
</script>
<template>
  <div id="app">
    <MyUnoob></MyUnoob>
    <MyUnoob></MyUnoob>
    <MyUnoob></MyUnoob>
  </div>
</template>

自定义事件:

vue2中通常使用this.$emit 来触发自定义事件并传递值给父组件,而在vue3中this是取不到值的,

可以使用 defineEmits 函数来声明组件的自定义事件

<template>
  <button @click="sendValue">点击发送值给父组件</button>
</template>

<script lang="ts" setup>
import { defineEmits } from 'vue';

const emits = defineEmits(['custom-event']); // 声明自定义事件

const sendValue = () => {
  const value = '这是子组件传递给父组件的值';
  emits('custom-event', value);
};
</script>
<template>
  <child-component @custom-event="handleCustomEvent"></child-component>
</template>

<script lang="ts" setup>
import ChildComponent from '@/components/ChildComponent.vue';
const handleCustomEvent = (value) => {
  console.log(value, '子组件传递过来的数据')
}
</script>

父子通信:

  • 父传子:在setup中使用props数据 setup(props){  props就是父组件数据 }

  • 子传父:触发自定义事件的时候emit来自 setup(props,{emit}){  emit 就是触发事件函数 

(1)父传子

   在setup函数中有两个参数 第一个参数为props,第二个参数为context

   props为一个对象,props接收后,内部包含了所有传递过来的prop数据,context包含了attrs,       slots,emit属性,其中emit方法可以完成子传父

//父亲

<template>
  <div>
    <Son :msg="msg"></Son>
  </div>
</template>
 
<script>
import { ref } from 'vue';
import Son from "./son.vue";
export default {
  components: {
    Son,
  },
  setup() {
      const msg = ref('奥特曼')
        return {msg}
  },
};
</script>
 
<style scoped>
</style>
//儿子

<template>
  <div>子组件 {{msg}} </div>
</template>
 
<script>
export default {
    props:{
        msg:{
            type:String,
            default:''
        }
    },
    setup(props,context){
        console.log(props,context);
    }
};
</script>
 
<style scoped>
</style>

(2)子传父

  注意vue2中的this.$emit 不在生效   setup函数中没有this ,而在setup第二个参数中有emit方法

//儿子

    setup(props,context){
        context.emit('name','奥特曼')
    }
 
//结构写法
//    setup(props,{emit}){
//       emit('name','奥特曼')
//   }
//父亲

<template>
    <Son @name="name" ></Son>
</template>
 
  setup() {
      const name = (name)=>console.log(name);
        return {name}
  },  

axios:

npm install axios

在src文件夹下面新建一个plugins文件夹,里面放一个axiosInstance.js文件

import axios from 'axios'
//创建axios实例
const service = axios.create({
	baseUrl:'http://localhost:8080' ,
	timeout: 2000                 
})
/**
 * 请求拦截
 */
service.interceptors.request.use(config => {
    config.headers['token'] = getToken('token') 
    return config
}, error => {
    return Promise.reject(error)
})

/**
 * 响应拦截
 */
service.interceptors.response.use(response => {
    if (response.data == null) {
        return response;
    }
    if (response.data.code == -2) {
        removeToken()
        router.push({
            name: 'myLogin'
        })
        return response;
    }
    return response.data
}, error => {
    return Promise.reject(error)
})
export default service

在main.js中引入:

import axios from '@/plugins/axiosInstance.js'
app.config.globalProperties.$axios = axios;  

在页面上面使用:

<script setup>
import service from "../plugins/axiosInstance"
const btn = () => {
  service({
    url: "/user/login",
    method: "post",
    data: {
      username: "xxxxxxxx",
      password: "xxxxx"
    }
  }).then(res => {
    if (res.code === 0) {
      console.log("请求成功!", res);
    } else {
      console.log("请求失败!", res.message);
    }
  });
};

使用 Vite 快速构建 Vue 项目:

Vite是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:

Vite 意在提供开箱即用的配置,同时它的 插件 API 和 JavaScript API 带来了高度的可扩展性,并有完整的类型支持。

Vite 天然支持引入 .ts 文件

npm init vite-app vue3-test //创建项目名称为vue3-test的项目
cd vue3-test //选择项目路径
npm install  //安装项目依赖
npm run dev  //打开项目

Vue3中的VueX

npm install vuex@next

新建文件夹store>index.ts

import { createStore } from "vuex";
interface State {
  stateName: string;
}
const store = createStore<State>({
  state() {
    return {
      // 定义初始状态
      stateName: localStorage.getItem('stateName')
    };
  },
  mutations: {
    // 定义修改状态的方法
    mutationName(state, stateName) {
      state.stateName = stateName;
      this.commit('saveTokenToStorage')//调用saveTokenToStorage方法,把stateName数据同步存储在本地
    },
    saveTokenToStorage(state) {
      localStorage.setItem('stateName', state.stateName);
    },
  },
  actions: {
    // 定义异步操作,可以在这里提交 mutation,//调用 dispatch 方法来触发
    asyncMutationName(context) {
      setTimeout(() => {
        context.commit('mutationName', '你好啊!!!!!!!!')
      }, 1000);
    },
  },
});
export default store;

main.ts中引入:

import store from "./store/index";
app.use(store);//引入创建的store,将其挂载到Vue实例上

在页面中使用:

 <a-input v-model:value="username.storeval"
placeholder="请输入store.state.stateName中的数据"></a-input>
{{ store.state.stateName }}

<a-button size="large" @click="hanldstore()">store</a-button>
<a-button size="large" @click="asyncIncrement()">asyncIncrement</a-button>

<script setup lang="ts">
import { useStore } from 'vuex'//使用useStore 函数来获取 Vuex 的 store 实例
const store = useStore()
const username = reactive({
  storeval: '',
})
const hanldstore = () => {
  store.commit('mutationName', username.storeval)
}
const asyncIncrement = () => {
  store.dispatch('asyncMutationName')//触发异步操作asyncMutationName
}
</script>

element-plus导航栏布局案例:

<template>
  <el-container class="home_container">
    <!-- 头部区域 -->
    <el-header style="background-color: #001529; display: flex;">
      <div class="hedlogo">
        <span>后台管理系统</span>
      </div>
      <div class="header-icon">
        <el-icon color="#fff"><icon-menu @click="collapsedbtn" /></el-icon>
      </div>
      <el-menu
        background-color="#001529"
        text-color="#fff"
        active-text-color="#1890ff"
        :default-active="$route.path"
        v-for="(m, index) in menus"
        :key="index"
        router
      >
        <el-menu-item v-if="!m.children" :index="m.path">
          {{ m.name }}
        </el-menu-item>
      </el-menu>
      <div class="header-logout">
        <el-button class="btn-blue2" type="info" @click="logout">
          退出
        </el-button>
      </div>
    </el-header>

    <!-- 页面主体区域 -->
    <el-container>
      <!-- 侧边栏 -->
      <el-aside :width="isCollapse ? '64px' : '200px'">
        <el-menu
          active-text-color="#1890ff"
          background-color="#fff"
          class="el-menu-vertical-demo"
          :default-active="$route.path"
          text-color="#333"
          v-for="(m, index) in menus"
          :collapse="isCollapse"
          router
          :key="index"
          @open="handleOpen"
          @close="handleClose"
        >
          <el-menu-item v-if="!m.children" :index="m.path">
            <el-icon><icon-menu /></el-icon>
            <span>{{ m.name }}</span>
          </el-menu-item>

          <el-sub-menu v-if="m.children" :index="index">
            <template #title>
              <el-icon><icon-menu /></el-icon>
              <span>{{ m.name }}</span>
            </template>
            <el-menu-item
              v-for="(mc, index) in m.children"
              :key="index"
              :index="mc.path"
              @click="handleOpen(mc.path)"
            >
              <el-icon><icon-menu /></el-icon>
              <span>{{ (mc.name)}}</span>
            </el-menu-item>
          </el-sub-menu>
        </el-menu>
      </el-aside>
      <!-- 右侧内容主体区域 -->
      <el-main>
        <div style="margin-bottom: 10px;">{{ $route.meta.title }}</div>
        <router-view></router-view>
      </el-main>
    </el-container>
  </el-container>
</template>
<script lang="ts">
import { Document, Menu as IconMenu } from '@element-plus/icons-vue'
import { defineComponent } from 'vue'
import { useRoute, useRouter } from 'vue-router'
export default defineComponent({
  name: 'HelloWorld',
  components: {
    IconMenu,
  },
  data() {
    return {
      isCollapse: false,
      menus: [
        {
          name: '首页',
          id: 1,
          children: [
            {
              name: '系统配置',
              id: 3,
              path: '/ListView',
            },
            {
              name: '菜单管理',
              id: 4,
              path: '/HomeView',
            },
          ],
        },
        {
          name: '表格列表',
          id: 4,
          path: '/AboutView',
        },
        {
          name: '登录',
          id: 5,
          path: '/HomeView',
        },
      ],
    }
  },
  methods: {
    collapsedbtn() {
      this.isCollapse = !this.isCollapse
    },
    handleOpen(key, keyPath) {
      console.log(key, keyPath, '00')
      const route = useRoute() // 在组件内部使用 useRoute()
      const router = useRouter() // 获取路由实例
      if (router) {
        router.push({
          path: key,
        })
      }
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath)
    },
    logout() {
      console.log('你已退出')
    },
  },
})
</script>
<style scoped lang="less">
.home_container {
  height: 100vh;
}
.hedlogo {
  height: 32px;
  width: 163px;
  border: 1px solid #000;
  background: #18224d;
  color: #fff;
  margin: 14px 14px 14px 0px;
  text-align: center;
  line-height: 32px;
}
.header-icon {
  text-align: center;
  width: 60px;
  line-height: 60px;
}
.header-logout {
  right: 10px;
  line-height: 60px;
  position: absolute;
}
.el-menu {
  border-right: 0px;
}
.el-aside {
  background-color: #fff;
}
.el-main {
  background-color: #f0f2f5;
  padding: 10px;
}
</style>

导航栏布局案例: 

<template>
  <a-layout id="components-layout-demo-custom-trigger">
    <a-layout-sider :width="isCollapse ? '75px' : '200px'">
      <div class="logo">后台管理系统</div>
      <a-menu
        theme="dark"
        mode="inline"
        :selectedKeys="[$route.path]"
        :openKeys="openKeys"
        v-for="m in menus"
        :key="m.path"
        @openChange="onOpenChange"
        @click="menuClick"
      >
        <a-menu-item v-if="!m.children" :key="m.path">
          <component :is="$icons['AppstoreOutlined']" />
          <span>{{ m.name }}</span>
          <router-link :to="m.path" :key="m.path"></router-link>
        </a-menu-item>
        <a-sub-menu v-if="m.children" :key="m.path">
          <template #title>
            <component :is="$icons['UserOutlined']" />
            <span>{{ m.name }}</span>
          </template>
          <a-menu-item v-for="mc in m.children" :key="mc.path">
            <component :is="$icons['FileDoneOutlined']" />
            <span>{{ mc.name }}</span>
            <router-link :to="mc.path" :key="mc.path"></router-link>
          </a-menu-item>
        </a-sub-menu>
      </a-menu>
    </a-layout-sider>
    <a-layout>
      <a-layout-header>
        <component
          style="padding: 10px;"
          :is="$icons['MenuOutlined']"
          @click="collapsedbtn"
        />
        <a-breadcrumb>
          <a-breadcrumb-item>{{ $route.meta.title }}</a-breadcrumb-item>
        </a-breadcrumb>
        <div class="header-logout">
          <a-button type="primary" @click="logout">
            退出
          </a-button>
        </div>
      </a-layout-header>
      <a-layout-content style="padding: 8px;">
        <router-view></router-view>
      </a-layout-content>
    </a-layout>
  </a-layout>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'HelloWorld',
  data() {
    return {
      isCollapse: false,
      menus: [
        {
          key: 'menu1',
          name: '首页',
          id: 1,
          children: [
            {
              key: 'menu1-1',
              name: '系统配置',
              id: 3,
              path: '/ListView',
            },
            {
              key: 'menu1-2',
              name: '菜单管理',
              id: 4,
              path: '/HomeView',
            },
          ],
        },
        {
          key: 'menu1-3',
          name: '表格列表',
          id: 4,
          path: '/AboutView',
        },
      ],
      openKeys: [],
    }
  },
  created() {
    const openKeys = window.sessionStorage.getItem('openKeys')
    if (openKeys) {
      this.openKeys = JSON.parse(openKeys)
    }
  },

  methods: {
    collapsedbtn() {
      this.isCollapse = !this.isCollapse
    },
    onOpenChange(openKeys) {
      window.sessionStorage.setItem('openKeys', JSON.stringify(openKeys))
      const latestOpenKey = openKeys.find(
        (key) => this.openKeys.indexOf(key) === -1,
      )
      if (latestOpenKey) {
        this.openKeys = latestOpenKey ? [latestOpenKey] : []
      }
    },
    menuClick({ key }) {
      this.$router.push({
        path: key,
      })
    },
    logout() {
      console.log('你已退出')
    },
  },
})
</script>
<style scoped lang="less">
#components-layout-demo-custom-trigger {
  font-size: 18px;
  line-height: 64px;
  min-height: 100vh;
  cursor: pointer;
  transition: color 0.3s;
  .logo {
    height: 32px;
    background: rgba(255, 255, 255, 0.2);
    margin: 16px;
    color: #fff;
    text-align: center;
    line-height: 32px;
  }
}
.ant-layout-header {
  height: 64px;
  padding: 10px;
  color: rgba(0, 0, 0, 0.85);
  line-height: 64px;
  background: #fff;
  display: flex;
  align-items: center;
}

.header-logout {
  right: 10px;
  line-height: 60px;
  position: absolute;
}
</style>

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐