Vue3.2 <script setup>用法总结

681 阅读3分钟

script setup

在 setup() 函数中手动暴露大量的状态和方法非常繁琐,我们可以使用 script setup 来大幅度地简化代码

<template>
</template>

<script setup>
</script>

<style lang="scss" scoped>
</style>

data

<script setup>
import { reactive,ref } from 'vue'

// 无响应式
let name = '张三'

 // reactive() 函数创建一个响应式对象或数组
const state = reactive({ count: 0 })

// ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0

const onClick1 = () => {
  state.count++
}

//
const onClick2 = () => {
  count.value++
}
</script>

<template>
  <button @click="onClick1">
    {{ state.count }}
  </button>
    <button @click="onClick2">
    {{ count }}
  </button>
</template>

method

<template>
  {{count}}
  <button @click='onClick'>按钮</button>  
</template>

<script setup>
  import { ref } from 'vue'

  const count = ref(0)

  // 声明method方法
  const onClick = () => {
    count.value++ 
  }  
</script>

computed

计算属性的出现是为了减少模板中出现冗余代码 computed和method做同样操作可以得到完全一样的结果,区别在于计算属性值会基于其响应式依赖被缓存

  • 用法一:只读
<template>
  <h1>{{ fullName }}</h1>
</template>

<script setup>
import { ref , computed } from 'vue'

const firstName = ref('Hello')
const lastName = ref('World!')

const fullName = computed(()=>{
  return firstName.value + '-' + lastName.value
})
</script>

computed 可以实现过滤器功能

<script setup>
import { reactive,computed } from 'vue'

const data = reactive({
    items: [
    {
        id: 0,
        title: 'Item A',
        list: 0
    },
    {
        id: 1,
        title: 'Item B',
        list: 0
    },
    {
        id: 2,
        title: 'Item C',
        list: 1
    }]
})
console.log(data);
const listLeft = computed(()=>{
    return data.items.filter(item => item.list === 1) // 将items数组中,过滤出list为1的,即id为2的项
})
 
const listRight = computed(()=>{
    return data.items.filter(item => item.list === 0) // 将items数组中,过滤出list为0的
})

console.log(listLeft.value)
console.log(listRight.value)
</script>
  • 用法二:可写
<template>
  <h1>{{ sum }}</h1>
  <button @click="sum=10">click</button>
</template>

<script setup>
import { ref , computed } from 'vue'

const count = ref('100')

const sum = computed({
  // getter 读取时调用
  get(){
    return count.value * 2
  },
  // setter 写入时调用
  set(newValue){
  	return count.value = newValue // 点击按钮 newValue==10
	}
})
</script>

watch

  • 监听 ref
<script setup>
import { ref, watch } from "vue"
const count = ref(0)
watch(
    count, // 注意这里变化
    (val,old) => {
        console.log('val',val);
        console.log('old',old);
    }
)
 
</script>
  • 监听 reactive
<script setup>
import { reactive, watch } from "vue"
const data = reactive({ count:0 })
watch(
    () => data.count, // 区别点
    (val,old) => {
        console.log('val',val);
        console.log('old',old);
    }
)
  • 开启深度监听和立即执行

    深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大,谨慎使用!

<script setup>
import { reactive, watch } from "vue"
const data = reactive({ count:0 })
watch(
    () => data.count, 
    (val,old) => {
        console.log('val',val);
        console.log('old',old);
    },
    {
        deep:   true, // 深度监听
        immediate:true // 立即执行
    }
)
</script>

动态样式

  • :class

      <!-- 1.对象方法 -->
      <div class="class1" :class="{class2:isShow,class3:true}">HelloWorld</div>
      <!-- 2.三元表达式方法 -->
      <div class="class1" :class="isShow ? 'class2' : 'class3'">HelloWorld</div>
      <!-- 3.数组方法 -->
      <div class="class1" :class="[class1,class2,class3]">HelloWorld</div>
    
  • :style

      <!-- 1.对象方法 -->
      <div :style="{ color: activeColor ,fontSize:size+'px'}">HelloWorld</div>
      <!-- 2.三元表达式方法 -->
      <div :style="{ color:(activeColor ? 'red' : 'blue')">HelloWorld</div>
      <!-- 3.数组方法 -->
      <div :style="[styles1,styles2]">HelloWorld</div>
    

css支持v-bind 指令

<template>
  <div class="box">{{color}}</div>
</template>

<script setup>
  import {ref} from 'vue'
  let color = ref('red')
</script>

<style scoped>
.box {
  width: 100px;
  height: 100px;
  background-color: v-bind(color);
}
</style>

生命周期

setup中的生命周期和Vue3.0使用没有区别

<script setup>
import { onMounted, onUnmounted } from 'vue'

onMounted(() => {})

onUnmounted(() => {})
</script>

Props

  • 子组件接收

在使用 <script setup> 的单文件组件中,props 可以使用 defineProps() 宏来声明

defineProps可以不导入,不过eslint可能报错

所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,会自动更新为最新的值,而不会逆向传递。 注意:不要在子组件修改prop;如果需要操作prop,可以在子组件先将prop值取出,存储到新变量中

<script setup>
const props = defineProps(['foo'])

// 设置类型与默认值
const props = defineProps({
name: {
    type: String,
    default: ''
}
})  
console.log(props.foo)
</script>

*************************************
// 也可以直接使用
defineProps({
  name: {
    type: Boolean,
    default: false
  },
  close: {
    type: Function,
    default: fun => fun()
  }
})

<div>{{ title }}</div>
<div @click="close(handleClose)">取消</div>
  • 父组件传递
<template>
  <child :name='张三'/>  
</template>

<script setup>
  // 引入子组件,组件会自动注册
  import child from './child.vue'
</script>

emit

  • 子组件

在使用 <script setup> 的单文件组件中,emit 可以使用 defineEmits() 宏来声明

<template>
  <button @click='onClick'>更名</button>
</template>

<script setup>
  const emit = defineEmits(['changeName'])
  
  const onClick = () => {
    // 第二次参数开始是需要传递的数据
    emit('changeName', 'Tom')
  }
</script>

**************************
// 语法糖形式
const emit = defineEmits(['update:show'])
const handleClose = () => {
  emit('update:show', false)
}

<div @click="close(handleClose)">取消</div>
  • 父组件
<template>
  <child :name='state.name' @updateName='updateName'/>  
</template>

<script setup>
  import { reactive } from 'vue'
  // 引入子组件
  import child from './child.vue'

  const state = reactive({
    name: 'Tom'
  })
  
  // 接收子组件触发的方法与参数
  const updateName = (name) => {
    state.name = name
  }
</script>
********************
// 语法糖形式
<child v-model:show="show"/>
<script setup>
    let show = ref(true)
</script>
在 Vue.js 中,父子组件之间的通信通常是通过 props 来实现的。当父组件中的数据发生改变时,它会向子组件传递新的 props,从而触发子组件的重新渲染。

当使用 $emit 方法向父组件传递对象时,如果直接将这个对象作为参数传递给 $emit,那么父组件中的数据并不会发生改变,也就失去了响应式。这是因为 Vue 是通过观察对象中的每一个属性来实现响应式的,如果直接将整个对象作为参数传递给 $emit,那么 Vue 无法监听到对象内部属性的改变。

为了解决这个问题,你需要在调用 $emit 方法时,将对象中的每一个属性分别作为参数传递给 $emit,这样 Vue 才能监听到每一个属性的变化,并触发父组件的重新渲染。

nextTick

等待下一次 DOM 更新刷新的工具方法

<script setup>
  import { nextTick } from 'vue'
	
  nextTick(() => {
    // ...
  })
</script>

defineExpose

使用 <script setup> 的组件是默认关闭的(即子组件的数据,父组件无法通过ref获取子组件数据);

可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性

  • 子组件
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

  • 父组件
<template>
  <child ref='childRef'/>
</template>

<script setup>
  import { ref, nextTick } from 'vue'
  // 引入子组件
  import child from './child.vue'
  
  // nextTick
  nextTick(() => {
    // 获取子组件值
    console.log(childRef.value.a)
    console.log(childRef.value.b)
  })
</script>

slot

  • 子组件
<template>
  // 匿名插槽
  <slot/>
  // 具名插槽
  <slot name='header'/>
  // 作用域插槽
  <slot name="footer" :scope="state" />
</template>

<script setup>
  import { useSlots, reactive } from 'vue'
  const state = reactive({
    name: '张三',
    age: '25岁'
  })
</script>
  • 父组件
<template>
  <child>
    // 匿名插槽
    <div>默认插槽</div>
    // 具名插槽
    <template #header>
      <div>具名插槽</div>
    </template>
    // 作用域插槽
    <template #footer="{ scope }">
      <footer>作用域插槽,{{ scope.name }}{{ scope.age }}</footer>
    </template>
  </child> 
</template>

<script setup>
  // 引入子组件
  import child from './child.vue'
</script>

useSlots() 和 useAttrs()

这两个辅助函数可以在 <script setup> 使用 slots 和 attrs

但是一般是直接通过 slotsslots 和 attrs 来访问它们

<script setup>
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()
</script>

顶层 await

<script setup>中可以使用顶层 await。结果代码会被编译成 async setup(),不必再写async

<script setup>
const post = await fetch(`/api/xxx`).then(() =>{})
</script>

路由

setup 里面没有访问 this,所以我们不能再直接访问 this.$routerthis.$route。作为替代,我们使用 useRouter 函数

<script setup>
  import { useRoute, useRouter } from 'vue-router'
	
  // 必须先声明调用
  const route = useRoute()
  const router = useRouter()
	
  // 路由信息
  console.log(route.query)

  // 路由跳转
  router.push('/home')
  router.push({
    name: 'home',
    query: {
      ...route.query,
    },
  })
</script>

vuex

定义数据

src/store文件夹下新建modules文件夹

modules文件夹下新建user.js文件

import { getUname } from '@/api/user'
// 用户
export default {
  // 开启命名空间
  namespaced: true,
  state: {
    info: {
      uname: 'Leo',
      age: 21
    },
    user_name:''
  },
  mutations: {
    updateUname(state, val) {
      state.info.uname = val
    },
    updateAge(state, val) {
      state.info.age = val
    },
    UPDATE_UNAME(state, val) {
      state.user_name = val
    }
  },
  actions: {
    asyncUpdate(store, val) {
      setTimeout(() => {
        store.commit('updateAge', val)
      }, 2000)
    }
    // 发起网络请求
    getUname({ commit, state }, val) {
      return new Promise((resolve, reject) => {
        getUname().then(response => {
          if (response.status == 1) {
            commit('UPDATE_UNAME', response.name)
            resolve()
          }
        }).catch(error => {
          reject(error)
        })
      })
    }
    ********************
    // 另一种形式
    getUname(context, val) {
        context.commit('updateUname',val)
    }
  }
}

  • src/store文件夹下创建getter.js
const getters = {
    user_name: state => state.user.user_name
}
export default getters
  • src/store文件夹下的index.js文件中引入上面两个文件
import { createStore } from 'vuex'
import getters from './getters'
import user from './modules/user.js'

export default createStore({
  getters,
  modules: {
    user
  }
})

使用

<template>
  <div>同步修改state:{{ $store.state.user.info.uname }}</div>
  <p>-----------------------------------------------</p>
  <div>异步修改state:{{ $store.state.user.info.age }}</div>
  <p>-----------------------------------------------</p>
  <div>getters数据:{{ $store.getters['user/format'] }}</div>
  <button @click="handleClick">修改</button>
</template>

<script>
import { useStore } from 'vuex'
export default {
  name: 'App',
  setup() {
    const store = useStore()
    const handleClick = () => {
      console.log(store)
      
      store.commit('user/updateUname', 'Tom')

      store.dispatch('user/asyncUpdate', 23)
      
      store.dispatch('user/getUname', 'zhangsan')
    }

    return { handleClick }
  }
}
</script>

  • 在src下的api定义接口请求
import request from './request'

export const getUname = (data) => {
  return request({
    url: '/xxx/xxx',
    method: 'POST',
    data
  })
}

vue-router

router-view

顾名思义: 路由视图,它实际上展示的是路由下面的子路由

  • router.js
routes: [
  {
    path: '/',
    name: 'HelloWorldA',
    component: HelloWorldA,
  },
  {
    path: '/helloWorldB',
    name: 'HelloWorldB',
    component: HelloWorldB,
    children: [{
      path: '',
      name: 'A',
      component: B
    }, {
      path: 'b',
      name: 'B',
      component: B
    },
    ]
  }
]
  • App.vue

展示的是 HelloWorldA

<template>
  <router-view />
</template>
  • HelloWorldA.vue
<template>
  <router-link to="HelloWorldB">跳转到 HelloWorldB</router-link>
</template>
  • HelloWorldB.vue

这里也有router-view,这里展示的是路由HelloWorldB的子路由,默认展示路由HelloWorldB下的A页面

<template>
  这是HelloWorldB
  <router-view />
</template>

路由跳转

  • router.js
const routes = [
    {
        path: '/',
        name: 'A',
        component: A
    },
    {
        path: '/B',
        name: 'B',
        component: () =>
            import ('../views/B.vue')
    }
]
  • 不传参跳转

通过router.push({})实现路由跳转

<template>
  <div @click="goToB">Page A</div>
</template>
<script setup>
import { useRouter } from "vue-router";
export default {
    const router = useRouter();
    const goToB = () => {
      router.push({
        name: "B",
      });
  }
};
</script>
  • 传参跳转
<template>
  <div @click="goToB">Page A</div>
</template>
<script setup>
import { useRouter } from "vue-router";
export default {
    const router = useRouter();
    const goToB = (value) => {
      router.push({
        name: "B",
        params:{
            value: value
        }
      });
  }
};
</script>
  • 接收参数

注意区分 useRouter 和 useRoute 的区别,一个末尾有 -r(用于跳转),一个没有(用于获取参数)

<template>
  <div>Page B</div>
</template>
<script setup>
import { onMounted } from 'vue';
import { useRoute } from "vue-router";
export default {
    const route = useRoute();
    const getParams = () => {
      return route.params;
    };
    onMounted(() => {
      console.log("mounted:" + getParams().value);
    });
};
</script>
  • 总结

传参

import { useRouter } from 'vue-router'
const router = useRouter()
// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?userId=123
router.push({ path: 'register', query: { userId: '123' }})

接收参数

import { useRoute } from 'vue-router'
//query 
let userId=route.query.userId; 
//params 
let userId=route.params.userId;
  • 补充

query和params区别 这两个都是跳转url的时候传递参数。

query可以用name和path来传递,但是params只能用name来传递,query是直接在url中,页面刷新后url传递的参数依旧在,params在刷新跳转页面的时候就没有了,下面实现一下具体的代码。

query url 格式:xxx.com/product?id=123 params url 格式:xxx.com/product/123

优劣:

  • 动态路由, 优点 ,好看整齐 缺点 必须预先定义, 如果参数多起来多起来不好管控
  • 问号的形式 灵活随意想改就改, 想加就加, 缺点就是太丑陋了, 也不直观

全局挂载属性

vue2中使用的方式

//在main.js文件
import  Vue  from 'vue';
import axios from 'axios';
Vue.prototype.$axios = axios;
 
//在其它组件使用,直接用this调用
this.$axios

vue3中使用的方式

  • 依赖注入 provide 和 inject 来全局挂载(推荐使用)
import { createApp } from 'vue'
import App from './App.vue'
import * as echarts from 'echarts';
let app = createApp(App);
 
// 全局挂载echarts
app.provide('$echarts',echarts);
 
app.mount('#app')

调用

<script setup>
import { inject, onMounted } from "vue";
 
//导入挂载的echarts 
const echarts = inject("$echarts");
 
onMounted(() => {
    let myChart = echarts.init(document.getElementById('main'));
    console.log(myChart);
});
</script>
  • vue3组合式里面(不推荐)

vue3全局挂载属性方式要通过globalProperties 官方不推荐这样使用,这样就当作vue2中的组合式API一样用this了

//vue3 main.js文件
import { createApp } from 'vue'
import App from './App.vue'
import * as echarts from 'echarts';
 
let app = createApp(App);
//全局挂载echarts属性
app.config.globalProperties.$echarts = echarts;
app.mount('#app')

调用

<script setup>
  import { getCurrentInstance } from 'vue'
 const { proxy } = getCurrentInstance();
 //调用
 proxy.echarts
</script>
  • 挂载到window
const app = createApp(App)
const im = im
window.$im = im

//调用
$im;

ref 获取子组件实例

<template> 
  <div ref="root">This is a root element</div>
</template>
<script setup>
  import { ref, onMounted } from 'vue'
  const root = ref(null)
  onMounted(() => {
    // DOM 元素将在初始渲染后分配给 ref
    console.log(root.value) // <div>This is a root element</div>
  })
</script>