vue3学习笔记(一)

前言

vue在20年9月发布了正式版本,一转眼一年过去了,终于有时间来学习3.0了,接下来做一些自己的学习笔记。

vue3对比vue2.x改变了什么

  • 响应式采用Proxy,不同于2.X的Object.defineProperty
  • composition API
  • 新的生命周期-LifeCycle Hooks
  • 更好的typescript支持
  • 明显的性能提升,打包更小、初次渲染更快、内存使用更少
  • 增加了新特性,如Teleport组件,全局API的修改和优化等

setup

准备工作:

  1. 首先使用vuecli搭建一个vue3project项目,选择语言为typescript,其他的vuex、vuerouter等都选上,之后等待创建完成。
  2. 来到Home.vue,把原有的代码都删除。

要让 TypeScript 正确推断 Vue 组件选项中的类型,需要使用 defineComponent 全局方法定义组件

import {defineComponent} from 'vue';

export default defineComponent({
  name: 'Home',
  setup() {
   
  }
});

setup() 函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3 的 Composition API 新特性提供了统一的入口, setup 函数会在 beforeCreate 、created 之前执行, vue3也是取消了这两个钩子,统一用setup代替, 该函数相当于一个生命周期函数,vue中过去的data,methods,watch等全部都用对应的新增api写在setup()函数中

setup()接收两个参数props和context,在接下来的组件中会说到。

需要注意的是在 setup() 内部,无法使用 this。 因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这使得 setup() 在和其它选项式 API 一起使用时可能会导致混淆。

响应性API

ref

接受一个内部值并返回一个响应式且可变的 ref 对象。

import {defineComponent, ref} from 'vue';

export default defineComponent({
  name: 'Home',
  setup() {
    // ref,定义一个响应式的数据(一般用来定义一个基本类型)
    const message = ref<string>('');
    // 打印 message 可以看到是一个Proxy对象
    console.log(message)
  }
});

reactive

返回对象的响应式副本

import {defineComponent, reactive} from 'vue';

interface UserForm {
  name: string,
  age: number,
  email: string;
}

export default defineComponent({
  name: 'Home',
  setup() {
    // reactive,定义多个数据的响应式,接收一个普通对象然后返回该普通对象的响应式代理器对象(Proxy)
    const state = reactive({
      men: {
        name: '张三',
        age: 18,
        email: '123456@qq.com'
      } as UserForm
    });
  }
});

把响应式对象展示在页面上

<template>
  <div class="home">
    Hellow World<br>
    <input type="text" v-model="message">
    <p>{{ message }}</p>
    <ul>
      <li>姓名:{{ state.men.name }}</li>
      <li>年龄:{{ state.men.age }}</li>
      <li>邮箱:{{ state.men.email }}</li>
    </ul>
    <button @click="ageAdd">点击使张三年龄+1</button>
  </div>
</template>

<script lang="ts">
import {defineComponent, ref, reactive} from 'vue';

interface UserForm {
  name: string,
  age: number,
  email: string;
}

export default defineComponent({
  name: 'Home',
  setup() {
    // ref,定义一个响应式的数据(一般用来定义一个基本类型)
    const message = ref<string>('');
    // reactive,定义多个数据的响应式,接收一个普通对象然后返回该普通对象的响应式代理器对象(Proxy)
    const state = reactive({
      men: {
        name: '张三',
        age: 18,
        email: '123456@qq.com'
      } as UserForm
    });
    // 打印一下state可以看到是一个Proxy对象
    // console.log(state);
    // 定义点击事件函数
    const ageAdd = (): void => {
      state.men.age++;
    };
    // 这里只要return出去就可以在template中使用了
    return {
      message,
      state,
      ageAdd
    };
  }
});
</script>

效果:可以看到当输入发生改变时会实时改变message的值,同时添加一个点击事件,每次点击都会使年龄+1。
在这里插入图片描述

生命周期

和2.x的生命周期作对比

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedonActivated
deactivatedonDeactivated

示例:

export default defineComponent({
  setup() {
    // 你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子
    onMounted(() => {
      console.log('onMounted');
    });
  }
});

组件和组件传参

假设现在我们在做一个商城项目,需要把商品列表做成一个组件,那么我们需要:

  1. 创建一个list组件,组件接收goodsList(商品列表)和page(分页)
  2. list组件需要触发父组件的pageChange事件,类似于vue2.x的this.$emit()

父组件:

<template>
  <div className="lifeCycle">
    lifeCycle{{ goodsList }}{{ page }}
    <List ref="ellist" :goodsList="goodsList" :page="page" @pageChange="pageChange"/>
  </div>
</template>

<script lang="ts">
import {defineComponent, onMounted, ref, reactive} from 'vue';
import List from '@/views/study/components/list.vue';

interface Data {
  name: string,
  age: number,
  email: string
}

interface Page {
  pageNo: number,
  pageSize: number,
  total: number
}
export default defineComponent({
  name: 'lifeCycle',
  components: {List},
  setup() {
    let goodsList = ref<Data[]>([]);
    let page = reactive({
      pageNo: 10,
      pageSize: 1,
      total: 0
    });
    const pageChange=(page:any)=>{
      console.log(page.pageNo)
    }
    // 获取真实dom
    const ellist = ref<null | HTMLElement>(null);
    // 你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子
    onMounted(() => {
      console.log(ellist);
    });
    // 模拟ajax
    setTimeout(() => {
      goodsList.value = [
        {
          name: '张三',
          age: 18,
          email: '1@qq.com'
        },
        {
          name: '李四',
          age: 19,
          email: '2@qq.com'
        }
      ];
      page.total = 10;
    }, 1000);

    return {
      goodsList, page,pageChange,ellist
    };
  }
});
</script>

</style>

子组件:
子组件通过props接收父组件的参数, content.emit()传递参数给父组件,这一点和2.x版本是类似的,只是写法不同。
setup(props,content)函数接收两个参数,props:组件传的参数。content:是一个普通的 JavaScript 对象,它暴露组件的三个 property,attrs,slots,emit。

<template>
  <div className="list">
    list{{ goodsList }}{{ page }}
    <table border="1">
      <tr>
        <th>姓名</th>
        <th>年龄</th>
        <th>邮箱</th>
      </tr>
      <tr v-for="item in goodsList" :key="item.email">
        <td>{{ item.name }}</td>
        <td>{{ item.age }}</td>
        <td>{{ item.email }}</td>
      </tr>
    </table>
    分页:
    <button @click="pageChange('upper')">上一页</button>
    <ul class="page">
      <li v-for="item in page.total" :key="item">{{ item }}</li>
    </ul>
    <button @click="pageChange('next')">下一页</button>
  </div>
</template>

<script lang="ts">
import {defineComponent, toRefs, ref, reactive, watchEffect, computed, PropType} from 'vue';

interface Page {
  pageNo: number,
  pageSize: number,
  total: number
}

export default defineComponent({
  name: 'list',
  props: {
    goodsList: Array,
    page: {
      type: Object as PropType<Page>
    }
  },
  setup(props, content) {
    const goodsList = computed(() => props.goodsList);
    const page = computed(() => props.page);
    const pageChange = (e: string) => {
      content.emit('pageChange', page.value);
    };
    return {
      goodsList, page, pageChange
    };
  }
});
</script>

<style lang="scss" scoped>
.page {
  display: flex;
}
</style>

Provide / Inject

通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。
对于这种情况,我们可以使用一对 provide 和 inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。

爷组件传值给子组件

假设我们的组建层级是这样的:

  • grandparent.vue
    • parent.vue
      • children.vue

爷组件需要传值给子组件需要这么做:

export default defineComponent({
  name: 'grandparent',
  setup() {
    const message = ref<string>('你好');

    // provide共享数据
    provide('message', message);
   
    return {
      message
    };
  }

子组件接收数据:

export default defineComponent({
  name: 'children',
  setup() {
    let message=inject('message');
    return { message }
  }
});

子组件修改爷组件的值

当子组件想要修改爷组件的值时:

<template>
  <div>
    子组件,接受爷组件传过来的值:{{message}}
    <button @click="updateMessage('子组件告诉你hello')">点击子组件改变爷组件的值</button>
  </div>
</template>

<script lang="ts">
import {defineComponent, ref, reactive,inject} from 'vue';

export default defineComponent({
  name: 'children',
  setup() {
    let message=inject('message');
    const updateMessage = inject('updateMessage')
    return { message,updateMessage }
  }
});
</script>

爷组件:

<template>
  爷组件,值为:{{ message }}
  <button @click="message='hello'">点击修改上方的值</button>
  <Parent/>
  <Children/>
</template>

<script lang="ts">
import {defineComponent, ref, reactive, provide, shallowRef,} from 'vue';
import Parent from '@/views/study/components/parent.vue';
import Children from '@/views/study/components/children.vue';

export default defineComponent({
  name: 'ProvideInject',
  components: {Parent, Children},
  setup() {
    const message = ref<string>('你好');

    // provide共享数据
    provide('message', message);
    // 接收子组件,子组件修改值
    provide('updateMessage', (val: string) => {
      message.value = val;
    });
    return {
      message
    };
  }
});
</script>

computed / watch

computed

接受一个 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。

<template>
  <div>
    <p>computeds</p>
    <p>count:{{ count }}</p>
    <p>plusOne.value:{{ plusOne }}</p>
    <p>plusOne是只读的,当count改变时会自动触发computed改变plusOne.value的值</p>
    <p>来试一下:
      <button @click="count++">点击使count+1</button>
    </p>
  </div>
</template>
<script lang="ts">
import {defineComponent, computed, ref, reactive, watch, watchEffect} from 'vue';

interface Obj {
  name: string
}

export default defineComponent({
  name: 'computedAndWatch',
  setup() {
    const count = ref<number>(1);
    
    const plusOne = computed(() => count.value + 1);

    return {
      count, plusOne
    };
  }
});
</script>

上面的写法plusOne是只读的,无法修改,我们也可以使用具有 get 和 set 函数的对象来创建可写的 ref 对象。

    const plusTwo = computed({
      get: () => count.value + 1,
      set: (val) => {
        count.value = val - 1;
      },
    });
    // 修改plusTwo的时候,count也会随之变化
    plusTwo = 100

watch

watch和2.x的 this.$watch 完全等效

export default defineComponent({
  name: 'computedAndWatch',
  setup() {
    const obj = reactive({
      name: '张三'
    } as Obj);
    
    let age = ref<string | number>('不知道');
    // 监听 obj 的值
    watch(obj, (newValue, oldValue) => {
      console.log(newValue, oldValue);
      if (newValue.name === '李四') {
        age.value = '18';
      }
    });
  }
});

当然,你也可以这样监听多个数据:

    watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {

    })

watchEffect

watchEffect和watch不同的是,watchEffect会立即运行

    // watchEffect会立即执行,自动检测内部代码,代码中有依赖 便会执行
    watchEffect(() => {
      if (obj.name === '李四') {
        age.value = '18';
      } else {
        age.value = '还没有';
      }
    });

hooks

在vue3之前,我们会采用mixins的方式封装一些公共的方法,但是mixins也有一些缺陷,比如:

  1. 容易冲突:因为每个特性的属性都被合并到同一个组件中,组件内同名的属性或方法会被mixins里的覆盖掉。
  2. 可重用性有限:我们不能向 mixins 传递任何参数来改变它的逻辑,这降低了它们在抽象逻辑方面的灵活性。
  3. 数据来源不清晰:组件里所使用的mixins里的数据或方法在当前组件代码里搜索不到,易造成错误的解读,比如被当成错误代码或冗余代码而误删。

在vue3出现了composition api之后,我们完全可以使用自定义hooks来替代mixins。

我们来感受一下hooks的使用方法,首先新建一个myHooks.ts:

import {reactive, ref,onMounted} from 'vue';

export const myHooks = () => {
  // ref,定义一个响应式的数据(一般用来定义一个基本类型)
  const message = ref<string>('111');
  console.log(message);
  // reactive,定义多个数据的响应式,接收一个普通对象然后返回该普通对象的响应式代理器对象(Proxy)
  const state = reactive({
    men: {
      name: '张三',
      age: 18,
      email: '123456@qq.com'
    }
  });
  onMounted(() => {
    console.log('hooks mounted')
  })
  return {
    message, state
  };
};

之后再需要的地方使用:

<template>
  <p>{{ message }}</p>
  <p>{{ state }}</p>
</template>

<script lang="ts">
import {defineComponent, ref, reactive, onMounted} from 'vue';
import {myHooks} from '@/hooks/myHooks';

export default defineComponent({
  name: 'hooks',
  setup() {
    // myHooks是个函数,需要执行一下
    const {message, state} = myHooks();

    onMounted(() => {
      console.log('index mounted');
    });

    return{
      message,state
    }
  }
});
</script>

获取真实的DOM

在vue2中我们使用 this.ref.xxx 获取组件的DOM,3.0也是用同样的方法,不过写法不一样

 <List ref="ellist"/>
 
   setup() {
    let goodsList = ref<Data[]>([]);
    // 获取真实dom
    const ellist = ref<null | HTMLElement>(null);
 
    onMounted(() => {
      console.log(ellist);
    });
  }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值