首先贴一下vue3
官网的地址 https://v3.cn.vuejs.org/guide/introduction.html#vue-js-%E6%98%AF%E4%BB%80%E4%B9%88
写过vue2
项目的都知道我们是如何写代码,但是可能很多人第一次用vue3
开发的时候难免会一头雾水,想着要怎么写呢?
其实在vue3里面用vue2的写法也是可以的,不过就是会报很多警告,项目还是可以运行的,不过不建议这么干,不然规范就毫无意义了。
上一篇我们创建了一个新项目,然后我们会发现里面的写法其实和我们vue2差不多,先来看看我们常用的几个吧
vue2
和 vue3
没有什么区别,一样是{{...}}
来实现文本插值
父组件:
子组件:
<div class="hello" v-bind:id="helloId">
<h1>{{ msg }}</h1>
</div>
和vue2一样,绑定标签属性的时候使用v-bind:
,比如常用的 :class
,:style
;等
v-model
v-model
指令用来在 input、select、textarea、checkbox、radio
等表单控件元素上创建双向数据绑定,根据表单上的值,自动更新绑定的元素的值
我们绑定v-model的时候一般都是直接:value
;:disabled
;:checked
等,这个提一下,在项目中遇到写到开关查询的时候,我采用了这样的方式v-model:checked="isUsed"
,猜一猜会发生什么事情?
当我需要这个开关在我做完某些操作之后才去进行状态变更,比如说@change
事件或者@click
事件之后,然后上述写法就会出现不受代码控制。
比如:isUsed
初始值为false
,点击开关之后我需要它依旧是关闭的,执行之后会发现isUsed
为true
,开关是开启状态
changeFun(){
isUsed = false
}
但是直接这么写 :checked="isUsed"
,isUsed
的值就会受代码影响,仍旧为false
。
所以vue3提供了两种方式,一种是直接响应式不受外界控制的,一种是可中断响应式的方式,所以在开发的时候需要注意。
和vue2写法一样,vue2怎么写、vue3就怎么写
我们开发很多中大型项目的时候,会经常遇到一些功能重复或者覆盖或者耦合的场景,于是我们会封装公共组件来减少代码的冗余,很多时候会注册全局组件。
vue2
项目 components
公共组件文件目录
import Vue from 'vue'
const componentList = require.context('.', true, /index.vue/)
const hyphenate = (name) => {
return name.replace(/\B([A-Z])/g, '-$1').toLowerCase()
}
componentList.keys().forEach(item => {
const componentItem = componentList(item).default
Vue.component(hyphenate(componentItem.name), componentItem)
})
上述代码获取 components
文件目录下我们封装的组件文件目录名称,然后Vue.component(...)
进行组件注册
vue3项目中我们同样可以注册全局组件,方式和vue2有所不同,采用下面的方式
Vue.createApp({...}).component('my-component-name', {
// ... 选项 ...
})
上述的代码仅仅把 Vue.component(hyphenate(componentItem.name), componentItem)
替换成 Vue.createApp({...}).component(hyphenate(componentItem.name), componentItem)
就可以了
以往vue2的项目开发中你一定知道props的一些传值方式了,来巩固一下
<HelloWorld msg="Welcome to Your Vue.js App"/>
变量值
表达式值
传数字
传布尔值
,值得注意的是
这样的写法需要在props里面设置该参数的类型为布尔值,否则拿到的将会是一个字符串
传对象
单个对象传值
将一个对象的所有 property 都作为 prop 传入 给定对象
data: {
id:1,
title:'111111'
}
传值 `<HelloWorld v-bind='data'/>`
等同于 `<HelloWorld :id='data.id' :title='data.title'/>`
注意:prop是一个单项数据流,是父子之间一个单向下行的数据绑定。每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。
我们都知道props接受参数的时候是可以定义该参数类型的,比如官方文档有列出来,借用一下
props: {
// 基础的类型检查 (`null` 和 `undefined` 值会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组的默认值必须从一个工厂函数返回
default() {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator(value) {
// 这个值必须与下列字符串中的其中一个相匹配
return ['success', 'warning', 'danger'].includes(value)
}
},
// 具有默认值的函数
propG: {
type: Function,
// 与对象或数组的默认值不同,这不是一个工厂函数——这是一个用作默认值的函数
default() {
return 'Default function'
}
}
}
当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。vue3和vue2中一样,拿到的参数是可以直接进行绑定在视图上的。
这个东西吧是个好东西,一定要想方设法在工作中用起来,因为它值得。
Vue
实现了一套内容分发的 API
,这套 API
的设计灵感源自 Web Components
规范草案,将
元素作为承载分发内容的出口。
举个例子:
比如说我们引入一个组件
<slot-bar>添加新的内容</slot-bar>
然而我们slotbar
这个组件里面呢是这样的
<div>
<slot></slot>
</div>
当组件渲染的时候
就会被‘添加新的内容’直接替换,当然除了字符串之外,插槽还可以渲染html代码,具体用法参考官网https://v3.cn.vuejs.org/guide/component-slots.html#%E6%8F%92%E6%A7%BD%E5%86%85%E5%AE%B9
当我们在vue2中实现一个是组件的时候,往往可能需要如下的代码,每个对应的模块都有他们自己应该做的事情,但是往往有些时候业务复杂,会导致代码很庞大,不利于阅读和理解,二次开发也十分不利
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
data () {
return {
repositories: [], // 1
filters: { ... }, // 3
searchQuery: '' // 2
}
},
computed: {
filteredRepositories () { ... }, // 3
repositoriesMatchingSearchQuery () { ... }, // 2
},
watch: {
user: 'getUserRepositories' // 1
},
methods: {
getUserRepositories () {
// 使用 `this.user` 获取用户仓库
}, // 1
updateFilters () { ... }, // 3
},
mounted () {
this.getUserRepositories() // 1
}
}
有过vue2开发经验的都非常清楚,结构很碎片化,一个功能我们需要阅读并且理解的话可以需要跳转好几个模块,这样阅读起来是很麻烦的。
于是,vue3
给我们提供了组合式API来解决这个问题,也就是setup
注意:在 setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取。
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
console.log(props) // { user: '' }
return {} // 这里返回的任何内容都可以用于组件的其余部分
}
// 组件的“其余部分”
}
我们以往在data中声明的对象可以再setup
中声明,我们在methods
中写的function
可以在setup
中编写,包括我们vue2
中的mounted、computed、watch
我们都是写在setup
中的。
以往我们vue2开发项目的时候,按照1中的代码结构,对应的钩子写对应的代码就可以了,但是vue3中不太一样,vue3为了避免我这样的懒癌患者偷懒写代码,于是提供了按需引入,接下来我们就看看我们在vue2开发过程中常用的一些方法在vue3中是如何实现的
我们如果要用到ref 必须先引入,不然控制台就会报错,defineComponent主要是针对ts的写法,使用jsx写的话可以不需要
<script>
import { ref } from "vue";
export default defineComponent({
});
</script>
引入之后我们便可以声明一个变量,我们对比一下vue2和vue3的区别
vue2:
export default {
data () {
return {
count: 1
}
},
mounted () {
console.log(this.count) // 1
}
}
vue3
<script>
import { ref } from "vue";
export default defineComponent({
setup(props, { emit }) {
const count = ref(1)
console.log(count)
},
});
</script>
你会发现vue2
中打印出来的count
值为1
,但是vue3
中不是,vue3
打印出来的结果如下
要获取到count的值,应该是console.log(count.value)
上述提到过setup
里面访问this
是访问不到的,所以我们数据绑定count到视图,我们需要将定义的参数return
出去
<script>
import { ref } from "vue";
export default defineComponent({
setup(props, { emit }) {
const count = ref(1)
console.log(count)
return {
count
};
},
});
</script>
除此之外,ref还可以这么用,ref就是用来创建响应式的
<script>
import { ref } from "vue";
export default defineComponent({
setup(props, { emit }) {
const count = ref(1)
const arrayList= ref([])
const flag= ref(false)
const title= ref('111111111111')
return {
count
};
},
});
</script>
综上:所有的用ref
所创建的响应式变量都需要.value
才能获取到值,改变值,比如count
的值改成2
,应该是count.value = 2
在vue3中要创建json对象的话只能用reactive
,不能用ref
,比如我们的查询参数
// 查询参数
const params = reactive({
page: 1,
pageSize: 10
});
同样的我们要使用的时候,要return出去
<script>
import { ref ,reactive} from "vue";
export default defineComponent({
setup(props, { emit }) {
const count = ref(1)
const arrayList= ref([])
const flag= ref(false)
const title= ref('111111111111')
const params = reactive({
page: 1,
pageSize: 10
});
return {
count,
params
};
},
});
</script>
reactive
创建的响应式json对象的参数不需要.value
去获取值和改变值,可以直接使用 params.page = 1
而在vue2中我们是如此:
export default {
data () {
return {
count: 1,
params:{
page:1,
pagesize:10
}
}
},
mounted () {
console.log(this.params.page) // 1
}
}
当你的数据不需要重新解构的话,一般情况用不上这个,按照正常的符合规范的开发逻辑,这个用处不大,但是具体业务场景需要用到的话,了解这个是解构方法就可以了
vue2:
export default {
data () {
return {
count: 1,
params:{
page:1,
pagesize:10
}
}
},
methods:{
getList(){
//通过接口获取列表数据
}
}
}
vue3
<script>
import { reactive,onMounted,computed,watch} from "vue";
export default defineComponent({
setup() {
const params = reactive({
page: 1,
pageSize: 10
});
//通过接口获取列表数据
funtion getList(){
}
return {
count,
params,
getList
};
},
});
</script>
vue2:
export default {
data () {
return {
count: 1,
params:{
page:1,
pagesize:10
}
}
},
mounted(){
this.getList()
},
methods:{
getList(){
//通过接口获取列表数据
}
}
}
vue3:
<script>
import { reactive,onMounted,computed,watch} from "vue";
export default defineComponent({
setup() {
const params = reactive({
page: 1,
pageSize: 10
});
//通过接口获取列表数据
funtion getList(){
}
const mountedFun = onMounted(() => {
getList();
});
return {
count,
params,
getList,
mountedFun
};
},
});
</script>
注意:vue3中直接使用onMounted
替换掉了vue2中的mounted
vue2:
export default {
data () {
return {
count: 1,
params:{
page:1,
pagesize:10
}
}
},
computed:{
pagination(){
return this.params
}
},
methods:{
getList(){
//通过接口获取列表数据
}
}
}
vue3:
<script>
import { reactive,onMounted,computed,watch} from "vue";
export default defineComponent({
setup() {
const params = reactive({
page: 1,
pageSize: 10
});
// 分页参数
const pagination = computed(() => ({
total: total.value,
current: params.page,
pageSize: params.pageSize
}));
//通过接口获取列表数据
funtion getList(){
}
const mountedFun = onMounted(() => {
getList();
});
return {
count,
params,
getList,
mountedFun,
pagination
};
},
});
</script>
vue2:
export default {
data () {
return {
count: 1,
params:{
page:1,
pagesize:10
}
}
},
computed:{
pagination(){
return this.params
}
},
watch:{
page(nval){
}
},
methods:{
getList(){
//通过接口获取列表数据
}
}
}
vue3:
<script>
import { reactive,onMounted,computed,watch} from "vue";
export default defineComponent({
setup() {
const params = reactive({
page: 1,
pageSize: 10
});
// 分页参数
const pagination = computed(() => ({
total: total.value,
current: params.page,
pageSize: params.pageSize
}));
//通过接口获取列表数据
funtion getList(){
}
const mountedFun = onMounted(() => {
getList();
});
watch(
() => params.page,
(newValue) => {
// params.page ++ 例子
}
);
return {
count,
params,
getList,
mountedFun,
pagination
};
},
});
</script>
综上我们可以看到,在vue3
中我们在setup
中声明的变量也好,定义的方法也好,都是需要return
的,不然控制台就会报警,导致意想不到的结果。
如果本文章让你不是很清晰,可以看官网https://v3.cn.vuejs.org/guide/composition-api-setup.html#%E5%8F%82%E6%95%B0
使用 setup
函数时,它将接收两个参数:props,context
。
props是响应式的,当传入新的
prop` 时,它将被更新。
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
如果有特定的业务场景需要解构props的话,你需要上面我们提到的torefs
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
如果 title 是可选的 prop,传入的 props 中没有 title ,toRefs
将不会为 title 创建一个 ref ,你需要使用 toRef
import { toRef } from 'vue'
setup(props) {
const title = toRef(props, 'title')
console.log(title.value)
}
context 是一个普通 JavaScript 对象,暴露了其它可能在 setup 中有用的值
export default {
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs)
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)
// 触发事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函数)
console.log(context.expose)
}
}
你也可以这么写
export default {
setup(props, { attrs, slots, emit, expose }) {
}
}
罗列一下基础知识点之后,我们会明显的发现vue3其实比vue2的碎片化更利于我们编写代码,避免错误,以及方便阅读和修改。
暂时写这么多,后续会持续更新的。
此文章可能会有不足,只是项目开发的一些记录,如果有什么错误,欢迎指出,我会及时更正。