美文网首页
一.vue3基础知识学习

一.vue3基础知识学习

作者: 一条小团团OvO | 来源:发表于2024-08-08 08:41 被阅读0次

项目环境搭建

这里使用vite 构建工具来构建项目

使用命令 npm init vite@latest 创建项目

image.png
填写项目名称
image.png
选择框架,这里选择vue
1721563105627.png
这里我选择ts,进入项目目录
这是创建完成的项目目录
1721563331802.png
使用 npm install 命令安装依赖,安装完成后目录:
1721563576516.png
使用命令 npm run dev 启动项目
1721563682885.png
按住ctrl点击那个网址打开页面
1721563763124.png
到这里项目就创建完成了,下面开始学习vue3的语法

一.学习setup语法糖模式

1.1如何创建一个响应式数据


<template>
  <div>
    <div>{{ aa }}</div>
  </div>
</template>

<script setup lang="ts">
  import {ref} from 'vue'
  const aa = ref("小王");
</script>

<style scoped>

</style>

效果


1721564427703.png

也可以使用vue2的写法,这里不做展示以后都是语法糖模式

二.基础指令(和vue 2基本不变,这里只做展示 ,详情请学习vue2)

v-for循环渲染元素
v-html设置元素的innerHtml,该指令会导致元素内容的模板内容失效
v-on注册事件简写为@
v-bind:绑定动态属性简写为::
v-show 控制元素的可见度
v-if v-else-if v-else 控制元素的生成
v-model 双向数据绑定 常用于表单元素v-on和v-bind结合版

<script setup lang="ts">
import {ref} from 'vue'
let a:number =1;//最简单的插值语法
let aa=ref(a);
const event="click";
const b:string ="我是一段文字";
const c=ref("小王");
const f:string ="<h1>ssss</h1>";
const aaa = ()=>{
  aa.value++;
  // c.value="小李"+a;
  console.log(a);
}
</script>
<template>
<!-- vue 3 支持vue2的写法 -->
 <div>
  <div >{{ aa }}</div>
  <input type="text" :value="a"/>
  <div>{{ c }}</div>
  <!-- <input type="text" v-model="c"/> -->
  <!-- <div v-text="b"></div>
  <div v-html="f"></div>
  <div v-if="a===1">true</div>
  <div v-if="a===2">false</div> -->
  <!-- v-if 操作dom v-show, 操作css v-show性能要高一点 -->
  <div v-show="a===1">显示</div>
  <div><button @click="aaa">尽情的点击我吧</button></div>
  <!-- <div><button @[event]="aaa">动态事件</button></div> -->

 </div>
</template>
<style scoped>
</style>

3.计算属性computed(两种写法)

<template>
  <div>
    <div>名字:{{ name }}</div>
    <button @click="change">修改</button>
    <form>
      <div>{{ form.name }}{{ form.age }}</div>
      <input type="text" v-model="form.name"/>
      <input type="text" v-model="form.age"/>
      <button @click.prevent="submitForm">提交</button>
    </form>
    <div>
      <div>{{ firstname }}</div>
      <div>{{ lastname }}</div>
      <div>{{ fullname }}</div>
      <button @click="changename">改变名字</button>
    </div>
  </div>
</template>
//1.选项式写法,支持一个对象传入get和set函数自定义操作
let firstname=ref("张");
let lastname=ref("三");
let fullname=computed<string>({
  get(){
    return firstname.value+lastname.value
  },
  set(value){
    const [firstName,lastName]=value.split(" ");
    firstname.value=firstName;
    lastname.value=lastName;
  }
})
const changename=()=>{
  fullname.value="李 四";
}
//2.函数式写法(不能修改fullname值)
let fullname=computed(()=>firstname.value+lastname.value)
const changename=()=>{
  firstname.value="李";
  lastname.value="四";//只能通过这种方式来改变值不能直接修改名字 
}

4.ref和reacvite

数据类型不同
ref:将一个普通的 JavaScript 值包装成响应式的引用类型。可以理解为 ref 是对普通值的包装。
reactive:将一个普通的 JavaScript 对象(或数组)转换为响应式代理对象。可以理解为 reactive 是对对象(或数组)的包装。
访问方式不同
ref:使用 .value 属性来访问和修改值。
reactive:可以直接访问和修改对象或数组的属性或元素,而无需使用 .value。
更新触发方式不同
ref:通过 ref() 或 .value 的赋值来触发更新。
reactive:通过直接修改对象或数组的属性或元素来触发更新。

import { ref, reactive } from 'vue';

// ref示例
const count = ref(0);
console.log(count.value); // 访问值
count.value += 1; // 修改值

// reactive示例
const state = reactive({
  name: 'Alice',
  age: 25,
});
console.log(state.name); // 访问属性
state.age += 1; // 修改属性

补充:ref 深层次的响应,shallowRef 浅层次的响应,triggerRef,customref
两个不能同时使用,会影响
ref支持所有类型reactive 只能传递引用数据类型
ref取值赋值都需要.value,reactive不需要

综合练习

完成案例:


01f1a9c3755d8158e41b72b802f24c4.png
<template>
  <div>
    <input type="text" placeholder="请输入要筛选的值" :value="keyword" @keyup.enter="aa($event)"/>
   <ul v-for="(item,index) in seraData" :key="item.name">
      <li >
          <span>名字:{{ item.name }}</span>
          <span>价格:{{ item.price }}数量:</span>
          <button @click="item.count>1?item.count--:item.count">-</button>
          <span>{{ item.count }}</span>
          <button @click="item.count<99?item.count++:item.count">+</button>
          <span>总价:{{ item.price*item.count }}</span>
          <button @click="removeItem(index)">删除</button>
      </li>
   </ul>
   <span>总价{{ total }}</span>
  </div>
</template>

<script setup lang="ts">
import { reactive,computed,ref } from "vue";
interface Data{
  name: string,
  price: number,
  count: number

}
let keyword=ref<string>("");
let data=reactive<Data[]>([
  {name: 'apple', price: 20, count: 3},
  {name: 'banana', price: 10, count: 5},
  {name: 'orange', price: 30, count: 2},
  {name: 'grape', price: 40, count: 1},
  {name: 'kiwi', price: 50, count: 4}
]);
let total=computed(()=>data.reduce((prev:number,next:Data)=>{
  return prev+next.price*next.count
},0))
const removeItem=(index:number)=>{
  data.splice(index,1);
}
const seraData=computed(()=>data.filter(item=>item.name.includes(keyword.value)))
const aa=(e:any)=>{
  keyword.value=e.target.value;
}
</script>

<style scoped>

</style>

五.组件

5.1组件生命周期

在Vue 3中,生命周期钩子被重命名并且分成了不同的阶段,以更好地描述它们的用途。这些新的生命周期钩子包括:
setup(): 这是一个新的入口点,在beforeCreate和created之前调用。

onBeforeMount/onMounted:组件挂载前/后的生命周期钩子。
onBeforeUpdate/onUpdated:组件更新前/后的生命周期钩子。
onBeforeUnmount/onUnmounted:组件卸载前/后的生命周期钩子。
onActivated/onDeactivated:组件被 keep-alive 缓存和恢复时调用。
onErrorCaptured:当捕获一个来自子孙组件的错误时被调用。

<template>
    <div ref="div">
        {{ name }}
        <h3>我是组件</h3>
        <button @click="change">名字</button>
    </div>
</template>

<script setup lang="ts">
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted,} from "vue";
//beforeCreate 和 created set语法糖模式是没有这两个生命周期的,setup 去代替
const name=ref("xiaowang");
const div=ref<HTMLDivElement>()
console.log("setup");
const change=()=>{
    name.value="xiaoli";
}
//创建钩子
onBeforeMount(() => {
    //创建之前是读不到dom的
    console.log("创建之前",div.value);
});

onMounted(() => {
    //创建完成才可以
    console.log("创建完成",div.value);
});
//更新
onBeforeUpdate(() => {
    //获取的是更新之前的dom
    console.log("更新之前",div.value?.innerText);
});

onUpdated(() => {
    console.log("更新完成",div.value?.innerText);
});
//销毁
onBeforeUnmount(() => {
    console.log("销毁之前");
});

onUnmounted(() => {
    console.log("销毁");
});
</script>

<style scoped>

</style>

5.2组件插槽

<template>
    <div>
        <!-- <SlotSub>我是默认插槽</SlotSub> -->
        <SlotSub>
            <template #[sname]>
                <h2>Header</h2>
            </template>
            <template #footer="data">
                <div>{{data}}</div>
            </template>
        </SlotSub>
    </div>
</template>

<script setup>
import SlotSub from './SlotSub.vue';
import {ref} from 'vue';

let sname=ref("header")
</script>

<style scoped></style>
<template>
    <div>
         <!-- 默认插槽 -->
         <slot></slot>
          <!-- 具名插槽 -->
         <slot name="header">Header content</slot>
         <div v-for="item in data " :key="item.id" >
            <slot name="footer" :data="item"></slot>
         </div>
        
    </div>
</template>
<script setup>
import {reactive} from 'vue';
const data=reactive([{
    id:1,
    name:'Alice',
    age:20
},
    {
    id:2,
    name:'Bob',
    age:22
}],
);
</script>

<style scoped>

</style>

5.3 父子组件传参

<template>
    <div>
        <span>{{ title }}</span>
        <span>{{ arr }}</span>
        <button @click="send">Emit</button>
    </div>
</template>

<script setup lang="ts">
// import {ref,} from "vue";
//父组件向子组件传值的两种方式
//1.不使用ts的方式
//  defineProps({
//     title: {
//         type: string,
//         required: true,
//     },
//  })
//2.使用ts的方式
defineProps<{title:string,
             arr:any[],
}>();
//3.使用ts 特有的方式给默认值withDefaults(defineProps,{默认值})
// withDefaults(defineProps<{title:string,
//              arr:any[]
// }>(),{title:"ni",arr:()=>["xiaomi"]})


//子组件给父组件传值
const eimt=defineEmits(['on-click',])
const send=()=>{
    eimt("on-click","12121")
}
//子组件还可以通过defineExpose来暴露一些属性和方法给父组件使用
defineExpose({
    myProp: "myValue",
    myMethod: () => {
        console.log("myMethod");
    },
})
</script>

<style scoped>

</style>
//=====parent<template>
    <div>
      <ParentSubParam :title="a" :arr="[1,2,3]" @on-click="dianji" ref="waterFall"/>
    </div>
  </template>
  
  <script setup lang="ts">
  import {ref} from "vue";
  import ParentSubParam from './components/ParentSubParam.vue';
  let a=ref("小李1");
  const dianji=(name:string)=>{
    console.log(name);
  }
  const waterFall=ref<InstanceType<typeof ParentSubParam>>();
  console.log(waterFall.value?.myProp);
  </script>
  
  <style scoped>
  provide/inject 依赖注入方式实现父子组件传参
  </style> 

5.4动态组件

<template>
    <div class="box">
        <div class="buttom" @click="curractive=0" :class="{active:curractive===0}">按钮a</div>
        <div class="buttom" @click="curractive=1" :class="{active:curractive===1}">按钮a</div>
        <div class="buttom" @click="curractive=2" :class="{active:curractive===2}">按钮a</div>
    </div>
    <!-- 创建动态组件 -->
     <component :is="components[curractive]"></component>
</template>

<script setup>
//markRaw标记一个对象使其永远不会成为响应式对象
import {ref,reactive,markRaw} from "vue"
import ComputedDemo from "./ComputedDemo.vue";
import ComponentsLife from "./ComponentsLife.vue";
import ComputedStart from "./ComputedStart.vue";
const curractive=ref(0);

const components = reactive({
    0: markRaw(ComputedDemo),
    1: markRaw(ComponentsLife),
    2: markRaw(ComputedStart)});
</script>

<style  scoped>
.buttom{
    width: 100px;
    height: 30px;
    line-height: 30px;
    text-align: center;
    border: 1px solid #ccc;
    margin: 5px;
    cursor: pointer;
    transition: background-color 0.3s ease;
}
.buttom:hover{
    background-color: #ccc;
}
.box{
    display: flex;
    flex-wrap: wrap;
    justify-content: space-around;
    box-shadow: 0 0 5px rgba(0,0,0,0.3);
}
.active{
    background-color: rgb(166, 162, 162);
}
</style>

六.vue-router

6.1.安装router运行命令 npm install vue-router -s

6.2使用路由实现页面跳转

//导入router createRouter, createWebHashHistory, RouteRecordRaw 
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routers: Array<RouteRecordRaw> = [{
    path: '/',
    name: 'home',
    redirect: '/login'//默认跳转路径
},
{
    path: '/regist',
    name: 'regist',
    component: () => import('../components/regist.vue')
},
{
    path: '/login',
    name: 'login',
    component: () => import('../components/login.vue')
},
{
    path: '/detail/:id/:name/:price',
    name: 'detail',
    component: () => import('../components/Detials.vue')
},
{
    path: '/UrlParamDemo',
    name: 'UrlParamDemo',
    component: () => import('../components/UrlParamDemo.vue'),
//     meta: {//路由元信息,里面可以放一些信息可以通过to.meta.title获取
//         title: '路由传参demo页面'
// }
},
]
const router = createRouter({
    history: createWebHistory(),//路由的模式
    routes: routers
})
export default router
<template>
    <div>
      <h1>router</h1>
      <!-- 1.通过path方式进行跳转 通过加replace属性使得跳转没有历史记录-->
      <!-- <router-link replace to="/">登录</router-link>
      <router-link to="/regist">注册</router-link> -->
      <!--2. 通过那么方式进行路由跳转 -->
       <!-- <router-link :to="{name:'home'}">登录</router-link>
       <router-link :to="{name:'regist'}">注册</router-link> -->
       <!-- 3.通过 a标签的方式进行页面跳转,这种方式会刷新页面,不推荐使用 -->
        <!-- <a href="/login">登录</a>
        <a href="/regist">注册</a> -->
        <!-- 4.通过js 的方式进行路由跳转 -->
         <button @click="toLogin">登录</button>
         <button @click="toRegist">注册</button>
         <button @click="prev">prev</button>
         <button @click="back">back</button>
      <!-- 路由匹配到的组件 会被渲染在这里 这是一个内置组件 -->
      <router-view></router-view>
    </div>
  </template>
  
  <script setup lang="ts">
  import {useRouter} from 'vue-router'
  const router = useRouter()
  const toLogin = () => {
    // router.replace('/login')通过这样的方式进行路由跳转不会留下历史记录
    router.push({name:'login'})
  }
  const toRegist = () => {
    // router.push('/regist')字符串方式
    // router.push({path:'/regist'})duing对象方式
    // router.push({name:'regist'}) name方式
    router.push({name:'regist'})
  }
  const prev = () => {
    //前进 router.go(1)  数字代表跳转的次数
  
    router.go(-1)
  }
  const back = () => {
     //后退 router.go(-1) 或者router.back(1)
    router.go(1)
  }
  </script>
  
  <style scoped>
  
  </style>

6.3 路由传参

取值为useRoute,设置值为useRouter

<template>
    <div class="container">
        <!-- 商品列表  {
            "name": "脚踩老坛酸菜",
            "price": 30,
            "count": 2
        }-->
            <div class="item" v-for="(item,index) in goods" :key="index">
                <span class="name">{{item.name}}</span>
                <span class="price">{{item.price}}</span>
                <span class="count">{{item.count}}</span>
                <span class="total">{{item.price * item.count}}</span>
                <button @click="detail(item)">详情</button>
            </div>
    </div>
</template>

<script setup lang="ts">
import{useRouter} from 'vue-router'
import {ref} from 'vue'
import {data} from './list.json'
const route = useRouter()
type Items = {
    id:string,
    name:string,
    price:number,
    count:number
}
const goods = ref(data)

const detail = (item:Items) => {
    // route.push({// 1.通过路由跳转携带参数
    //     path:'/detail',
    //     query:item
    // })
    route.push({// 2.通过路由跳转携带参数
        name:'detail',
        params:item
    })
}
</script>

<style scoped>

</style>
<template>
    <div>
     <h1>我是详情页</h1>
     <div>
        <span>名称:</span>
        <span>{{data.name}}</span><br>
        <span>价格:</span>
        <span>{{data.price}}</span>
     </div>
     <router-link :to="{name:'UrlParamDemo'}">返回</router-link>
    </div>
</template>

<script setup lang="ts">
import {useRoute} from 'vue-router'
const route = useRoute()
// const data = route.query
// const data = route.params 该方式已经取消使用了
//如果参数较少可以使用动态路由的方式来传参
const data = route.params
</script>

<style scoped>

</style>

6.4路由守卫(前置守卫,后置守卫)

import { createApp,createVNode,render } from 'vue'
import App from './App.vue'
import router from './router/index'
import loadingBar from './components/loadingBar.vue'
const vNode=createVNode(loadingBar)//创建一个虚拟节点
render(vNode,document.body)//渲染到真是dom
const app=createApp(App)
      app.use(router)
      app.mount('#app')
const passPath=['/login','/regist','/']
router.beforeEach((to,from,next)=>{//路由前置守卫
    vNode.component?.exposed?.startLoading()//开始加载
    if(passPath.includes(to.path) || localStorage.getItem('token')){//如果路径在passPath中或者已经登录过了,则放行
        next()
    }else{
        next('/')//否则跳转到登录页面
    }
})
router.afterEach((to,from)=>{//路由后置守卫,应用场景loding bar
    // console.log(to,from)
    vNode.component?.exposed?.endLoading()//结束加载
})

通过路由守卫实现一个页面加载进度条

<template>
    <div class="wraps">
        <div class="bar" ref="bar">
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, } from 'vue'
let bar = ref<HTMLElement>()
let speed = ref<number>(0);
let timer = ref<number>(0);

const startLoading = () => {
    let ba = bar.value as HTMLElement
    setTimeout(() => {
        speed.value = 1
        timer.value = window.requestAnimationFrame(function fn() {
            if (speed.value < 100) {
                ba.style.width = speed.value + '%';
                speed.value += 1;
                timer.value = window.requestAnimationFrame(fn)

            } else {
                speed.value = 0
                window.cancelAnimationFrame(timer.value)
            }
        });
    }, 1000)

}
const endLoading = () => {
    let ba = bar.value as HTMLElement
    setTimeout(() => {
        window.requestAnimationFrame(() => {
            speed.value = 100
            ba.style.width = speed.value + '%';
        })
    }, 2000)
}
defineExpose({
    startLoading,
    endLoading
})
</script>

<style scoped lang="less">
.wraps {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 2px;
    // background-color: #409eff;
    z-index: 999;

    .bar {
        height: 2px;
        background-color: #409eff;
        width: 1%;
    }
}
</style>

6.5路由滚动行为

当切换新路由时如果想要滚动到顶部或者保持原先的位置,就像重新加载页面那样router 可以提供一个 scrollBehavior方法定义切换路由时如何滚动

6.6动态路由

可以通过动态路由的方式来实现权限控制

七.pinia 数据仓库

1.初始化vite-cli
npm init vite@latest
2.根据提示创建vue+ts的项目
3.npm install 安装项目需要的依赖
4.启动项目npm run dev
5.安装pinia
npm install pinia -s
6.导入并使用
import { createPinia } from 'pinia';
const store = createPinia();
let app=createApp(App)
app.use(store)
app.mount('#app')
7.pinia修改值的五种方式
1.直接修改
2.通过test.patch({})的方式修改 3.通过test.patch((state)=>{
//工厂函数的方式去修改值,函数里可以做一些逻辑处理
})//推荐使用这种方式
4.test.$state={}//有缺陷必须全部修改,一般不使用这种方式
5.借助actions修改
解构出来的数据是不具有响应式的这一点和reactive是一样的
可以通过storeToRefs包裹解决这一点

import { defineStore } from "pinia";
//导入命名枚举
import { Names } from "./storeNameSpace";

export const userTestStore=defineStore(Names.Test,{
    state:()=>{
        return {
            name:'你好',
            current:1,
        }
    },
    //类似于computed 可以用来修饰一些值
    getters:{
    },
    //actions类似于methods 用来写一些方法,同步异步都可以
    actions:{
    },
});
<template>
  <div>
    {{ test.current }}----{{ test.name }}
  </div>
</template>

<script setup lang="ts">
import { userTestStore } from './store/demo';
const test=userTestStore();
</script>

<style scoped>

</style>
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia';
const store = createPinia();
let app=createApp(App)
app.use(store)
app.mount('#app')

相关文章

网友评论

      本文标题:一.vue3基础知识学习

      本文链接:https://www.haomeiwen.com/subject/plzjhjtx.html