Vue3学习(创建vue3项目和常用的Composition API)

一. 创建Vue3.0工程

1. 使用vue-cli创建

vue create vue_test

2. 使用vite创建 (暂时了解)

  • vite —— 新一代前端构建工具 https://vitejs.cn

  • 创建工程:

    npm init vite-app <project-name>
    cd <project-name>
    npm install
    npm run dev
    

二. 常用Composition API(组合式API)

1. 学习setup

  1. Vue3.0中一个新的配置项,值为一个函数

  2. 组件中所有用到的数据、方法等,均配置在setup中

  3. setup函数的两种返回值:

    • 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。

      <template>
        <h1>我是App组件</h1>
        <p>姓名: {{name}}</p>
        <p>年龄: {{age}}</p>
        <button @click="sayHello">vue3中的sayHello</button>
      </template>
      ​
      <script>
      export default {
        name: 'App',
        setup() {
          // 不是响应式的数据
          let name = 'Tom';
          let age = 2;
      ​
          function sayHello() {
            alert(`我是${name}, 今年${age}岁!`);
          }
          // 返回一个对象, 模板中可以直接使用
          return {
            name,
            age,
            sayHello
          }
        }
      }
      </script>

    • 若返回一个渲染函数:则可以自定义渲染内容。(了解)

      <template>
        <h1>我是App组件</h1>
      </template>
      ​
      <script>
      // 需要引入h
      import {h} from 'vue';
      ​
      export default {
        name: 'App',
        setup() {
          // 返回一个渲染函数, 会使模板里写的实效, 只显示下边的h1标签
          return () => h('h1', '这是渲染函数')
        }
      }
      </script>
  4. 注意点:

    • 尽量不要与Vue2配置混用 (2可以访问3的setup中的属性和方法;3不能访问2;如果有重名3优先)

      <template>
        <h1>我是App组件</h1>
        <p>姓名: {{name}}</p>
        <p>年龄: {{age}}</p>
        <p>性别: {{sex}}</p>
        <p>a的值: {{a}}</p><!--显示的是200-->
        <button @click="sayHello">vue3中的sayHello</button>
        <br>
        <br>
        <button @click="sayWelcome">vue2中的sayWelcome</button>
        <br>
        <br>
        <button @click="test1">测试vue2能否读取vue3的数据</button>
        <br>
        <br>
        <button @click="test2">测试vue3能否读取vue2的数据</button>
      </template>
      ​
      <script>
      export default {
        name: 'App',
        data() {
          return {
            sex: '男',
            a: 100
          }
        },
        methods:{
          sayWelcome() {
            alert('hello~ i am vue2')
          },
          test1() {
            console.log(this.sex, this.name, this.age, this.sayHello); 
            // 男 Tom 2 ƒ sayHello()
            this.sayHello();
          }
        },
        setup() {
          // 不是响应式的数据
          let name = 'Tom';
          let age = 2;
          let a = 200;
      ​
          function sayHello() {
            alert(`我是${name}, 今年${age}岁!`);
          }
          function test2() {
            console.log(this.sex, this.sayWelcome, this.name, this.age); 
            // undefined undefined 'Tom' 2
          }
          // 返回一个对象, 模板中可以直接使用
          return {
            name,
            age,
            a,
            sayHello,
            test2
          }
        }
      }
      </script>
    • setup最好不要是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性。

2. ref函数

  • 作用:定义一个响应式数据

  • 语法:const/let xxx = ref(initValue); 

    • 创建一个包含响应式数据的引用对象(reference对象)

    • js中操作数据:xxx.value

    • 模板中读取数据直接写:<div> {{xxx}} </div>

  • 接受数据:

    • 基本类型数据:响应式靠Object.defineProperty()getset完成

    • 引用类型数据:内部“求助”了Vue3中的一个新函数——reactive函数

<template>
  <h1>我是App组件</h1>
  <p>姓名: {{name}}</p>
  <p>年龄: {{age}}</p>
  <h3>工作: {{info.job}}</h3>
  <h3>成就: {{info.achievement}}</h3>
  <button @click="changeVal">修改值</button>
</template>
​
<script>
import {ref} from 'vue';
​
export default {
  name: 'App',
  setup() {
    // ref函数实现响应式
    let name = ref('Tom');
    let age = ref(2);
    let info = ref({
      job: 'eat mouse',
      achievement: 'zero mouse'
    })
​
    // 方法
    function changeVal() {
      console.log(name, age); // 包装在引用对象中RefImpl
      name.value = 'Jerry';
      age.value = 1;
      console.log(info.value); // 包装在Proxy中
      info.value.job = 'steal cake';
      info.value.achievement = 'countless cakes'
    }
    
    return {
      name,
      age,
      info,
      changeVal
    }
  }
}
</script>

3. reactive函数

  • 作用:定义一个对象类型的响应式数据(基本类型无法使用,用ref函数)

  • 语法:consst 代理对象 = reactive(源对象)

    • 接受一个对象(数组)

    • 返回一个代理对象(Proxy的实例对象,简称proxy对象)

  • 其他

    • reactive定义的响应式数据是“深层次的”

    • 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作

<template>
  <h1>我是App组件</h1>
  <p>姓名: {{animal.name}}</p>
  <p>年龄: {{animal.age}}</p>
  <h3>工作: {{animal.info.job}}</h3>
  <h3>成就: {{animal.info.achievement}}</h3>
  <h3>测试c: {{animal.info.a.b.c}}</h3>
  <button @click="changeVal">修改值</button>
</template>
​
<script>
import {reactive} from 'vue';
​
export default {
  name: 'App',
  setup() {
    // reactive实现对象的响应式
    let animal = reactive({
      name: 'Tom',
      age: 2,
      info: {
        job: 'eat mouse',
        achievement: 'zero mouse',
        a: {
          b: {
            c: 400
          }
        }
      },
      hobby: ['play', 'eat', 'sleep']
    })
​
    // 方法
    function changeVal() {
      console.log(animal);
      animal.name = 'Jerry';
      animal.age = 1;
      animal.info.job = 'steal cake';
      animal.info.achievement = 'countless cakes';
      animal.info.a.b.c = 666;
    }
    
    return {
      animal,
      changeVal
    }
  }
}
</script>

4. Vue3.0 中的响应式原理

(1)Vue2.0 中的响应式原理

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)

    • 数组类型:通过重写更新数组的一系列方法实现拦截(对数组的变更方法进行了包裹)

      Object.defineProperty(data, 'count', {
          get() {},
          set() {}
      })
  • 存在问题:

    • 新增属性、删除属性,界面不会自动更新。(解决:利用this.$set() this.$delete()Vue.set() Vue.delete()实现

    • 直接通过下标改数组,界面不会自动更新。(解决:利用this.$set()或数组的方法XX.splice()实现

<template>
  <div>
    <h1>Vue2 响应式原理</h1>
    <h2 v-show="cat.name">姓名: {{cat.name}}</h2>
    <h2>年龄: {{cat.age}}</h2>
    <h2 v-show="cat.sex">性别: {{cat.sex}}</h2>
    <h2>爱好: {{hobby}}</h2>
    <button @click="addValue">增加对象属性</button>
    <button @click="deleteValue">删除对象属性</button>
    <button @click="modifyValue">修改数组中的值</button>
  </div>
</template>
​
<script>
import Vue from 'vue'
​
export default {
  data() {
    return {
      cat: {
        name: 'Tom',
        age: 2,
      },
      hobby: ['eat', 'sleep']
    }
  },
  methods: {
    addValue() {
      // this.cat.sex = 'boy';  // 无效
      // this.$set(this.cat, 'sex', 'boy')
      Vue.set(this.cat, 'sex', 'boy')
    },
    deleteValue() {
      // delete this.cat.name  // 无效
      // this.$delete(this.cat, 'name')
      Vue.delete(this.cat, 'name')
    },
    modifyValue() {
      // this.hobby[1] = 'play'  // 无效
      // this.$set(this.hobby, 1, 'play')
      this.hobby.splice(1, 1, 'play');
    }
  }
}
</script>
// vue2中的响应式
let person = {
    name: 'Tom',
    age: 20
}
let p = {};
​
let perProps = Object.keys(person);
// console.log(perProps); 拿到包含key值的数组
perProps.forEach(item => {
    Object.defineProperty(p, item, {
        get() {
            console.log('获取了' + item);
            return person[item]
        },
        set(val) {
            console.log('修改了' + item);
            person[item] = val
        }
    })
})

(2)Vue3.0 中的响应式

  • 实现原理:

    • 通过Proxy代理:拦截对象中任意属性的变化(读、增、改、删)

    • 通过Reflect反射:对源对象的属性进行操作

let person = {
    name: 'Tom',
    age: 20
}
let p = new Proxy(person, {
    get(target, propsName) {
        console.log(`获取了p里的某个属性${propsName}`);
        // return target[propsName]
        return Reflect.get(target, propsName)
    },
    set(target, propsName, newVal) {
        console.log(`修改或增加了p里的某个属性${propsName}`);
        // target[propsName] = newVal
        Reflect.set(target, propsName, newVal)
    },
    deleteProperty(target, propsName) {
        console.log(`删除了p里的某个属性${propsName}`);
        // return delete target[propsName]
        return Reflect.deleteProperty(target, propsName)
    }
})

5. reactive和ref对比

reactiveref
定义数据引用类型数据基本数据类型,也可以定义对象,内部自动通过reactive转为代理对象
实现原理Proxy实现响应式,Reflect操作源对象数据Object.defineProperty()getset实现响应式
使用方式操作数据不需要.value操作数据需要.value,模板中不需要.value

6. setup的参数

  • 父组件

<template>
  <Demo @hello="getHello" msg="信息" name="名字">
    <span>Tom cat</span>
  </Demo>
</template>
​
<script>
import Demo from './component/Demo.vue'
export default {
  name: 'App',
  components: {Demo},
  setup() {
    function getHello(val) {
      alert(`app接受到了demo传的值: ${val}`)
    }
​
    return {
      getHello
    }
  }
}
</script>
  • 子组件

<template>
  <button @click="sendValue">点击demo给app传值</button>
</template>
​
<script>
export default {
  name: 'Demo',
    props: ['msg', 'name'],
    emits: ['hello'], // 不加也不报错
    // beforeCreate() {
        // console.log('----beforeCreate----');
    // },
    setup(props, context) {
        // console.log('----setup----', this); // 比beforeCreate先执行, 且this为undefined
        console.log(props); // Proxy {msg: '信息', name: '名字'} 从父组件传来的值
        // console.log(context.attrs); // 当没有props接受传递的值, 就能拿到值
        console.log(context.slots);
​
        function sendValue() {
            context.emit('hello', 666)
        }
        return {
            sendValue
        }
    }
};
</script>
  • setup执行的时机—— beforeCreate前执行一次, this是undefined

  • setup的参数:

    • props:值为对象,仅包含显性声明的 prop(无论是否传递值,没有传递的值为undefined

    • context:上下文对象

      • attrs:值为对象,组件外部传递且没有在props配置总声明的属性

      • slots:收到的插槽内容

      • emit:分发自定义事件的函数

7. 计算属性

<template>
  姓: <input type="text" v-model="person.firstname">
  <br>
  名: <input type="text" v-model="person.lastname">
  <br>
  全名: <input type="text" v-model="person.fullname">
  <br>
  <h3>全名: {{person.fullname}}</h3>
</template>
​
<script>
import { reactive, computed } from 'vue';
​
export default {
  name: 'App',
  setup() {
    let person = reactive({
      firstname: '张',
      lastname: '三'
    })
​
    // 计算属性 ---- 简写, 只能读取
    // person.fullname = computed(() => {
    //   return person.firstname + person.lastname;
    // })
​
    // 计算属性 ---- 全写, 能读能改
    person.fullname = computed({
      get() {
        return person.firstname + '-' + person.lastname
      },  
      set(val) {
        const disName = val.split('-');
        person.firstname = disName[0];
        person.lastname = disName[1];
      }
    })
​
    return {
      person
    }
  }
}
</script>

8. watch监视

  • 小坑

    • 监视reactive定义的响应式数据时, oldValue无法正确获取, 强制开启了深度监视(deep配置失效

    • 监视reactive定义的响应式数据中某个属性时,deep配置有效

  • ref定义的基本数据不需要.value, 因为这样获取到的值是一个具体的值(字符串/数字等)

  • ref定义的对象, 要去监视需要.value, 否则无法监控到对象里面的值得变化, 或者去配置深度监视{deep: true}

<template>
  <p>求和: {{sum}}</p>
  <button @click="sum++">点击加1</button>
  <hr>
  <p>科目: {{subject}}</p>
  <button @click="subject += '!'">点击改变</button>
  <hr>
  <p>姓名: {{person.name}}</p>
  <p>年龄: {{person.age}}</p>
  <button @click="person.name += '~'">改姓名</button>
  <button @click="person.age ++">改年龄</button>
  <hr>
  <h4>薪水: {{person.job.j.salary}}K</h4>
  <button @click="person.job.j.salary ++">涨薪</button>
</template>
​
<script>
import { reactive, ref, watch  } from 'vue';
​
export default {
  name: 'App',
  setup() {
    let sum = ref(0);
    let subject = ref('数学');
    let person = reactive({
      name: 'Tom',
      age: 18,
      job: {
        j: {
          salary: 20
        }
      }
    })
​
    // 监视1, 单个简单类型数据
    /* watch(sum, (newVal, oldVal) => {
      console.log('sum变了', newVal, oldVal);
    }, {immediate: true}) */
​
    // 监视2, 多个简单类型数据
    // watch([sum, subject], (newVal, oldVal) => {
    //   console.log('sum 和 subject变了', newVal, oldVal);
    // })
​
    // 监视3, 监视reactive数据;
    // watch(person, (newVal, oldVal) => {
    //   console.log('person改变了', newVal, oldVal); // oldVal失效
    // })
​
    // 监视4, 监视对象的某个属性(简单类型)
    // watch(() => person.name, (newVal, oldVal) => {
    //   console.log('person里的名字变了', newVal, oldVal);
    // })
​
    // 监视5, 监视对象的多个属性(简单类型)
    // watch([() => person.name, () => person.age], (newVal, oldVal) => {
    //   console.log('person里的name和age变了', newVal, oldVal);
    // })
​
    // 监视6, 监视对象里的属性(非简单类型)
    watch(() => person.job, (newVal, oldVal) => {
      console.log('person里的job变了', newVal.j.salary, oldVal.j.salary);
    }, {deep: true})
    watch(person, (newVal, oldVal) => {
      console.log('person变了', newVal, oldVal);
    }, {deep: true})
​
    return {
      sum,
      subject,
      person
    }
  }
}
</script>
  • watchEffect函数

    • 不指明监视那个属性, 监视的回调中用到哪个属性, 就监视那个属性

    • 类似computed

      • computed注重的计算出来的值, 必须写返回值

      • watchEffect更注重的是过程(回调函数的函数体), 不写返回值

<template>
  <p>求和: {{sum}}</p>
  <button @click="sum++">点击加1</button>
  <hr>
  <p>科目: {{subject}}</p>
  <button @click="subject += '!'">点击改变</button>
  <hr>
  <p>姓名: {{person.name}}</p>
  <p>年龄: {{person.age}}</p>
  <button @click="person.name += '~'">改姓名</button>
  <button @click="person.age ++">改年龄</button>
  <hr>
  <h4>薪水: {{person.job.j.salary}}K</h4>
  <button @click="person.job.j.salary ++">涨薪</button>
</template>
​
<script>
import { ref, reactive, watchEffect  } from 'vue';
​
export default {
  name: 'App',
  setup() {
    let sum = ref(0);
    let subject = ref('数学');
    let person = reactive({
      name: 'Tom',
      age: 18,
      job: {
        j: {
          salary: 20
        }
      }
    })
​
    watchEffect(() => {
      const s1 = sum.value;
      const s2 = person.name;
      const s3 = person.job.j.salary
      console.log('有值变化了');
    })
    
    return {
      sum,
      subject,
      person
    }
  }
}
</script>

9. 生命周期

  • Vue3中可以继续使用Vue2的生命周期钩子, 最后两个名字变更,

    • beforDestroy变为beforeUnmount,

    • destroyed变为unmounted

  • Vue3中提供组合API形式的生命周期钩子

    • beforeCreate ===> setup()

    • created ===> setup()

    • beforeMount ===> onBeforeMount

    • mounted ===> onMounted

    • beforeUpdate ===> onBeforeUpdate

    • updated ===> onUpdated

    • beforeUnmount ===> onBeforeUnmount

    • unmounted ===> onunMounted

10. 自定义hook函数

  • 本质是一个函数, 把setup函数中使用的Composition API进行封装

  • 优点: 复用代码, 让setup中的逻辑更清楚

  • 类似Vue2中的mixin

// hooks文件夹里的自定义hook函数--savePoint.js
import { onBeforeUnmount, onMounted, reactive } from 'vue';
​
export default function() {
    // 保存鼠标点击的坐标点的数据
    let point = reactive({
        x: 0,
        y: 0
    })
​
    // 事件方法
    function savePoint(event) {
        console.log(event.pageX, event.pageY);
        point.x = event.pageX;
        point.y = event.pageY;
    }
​
    // 挂载完成, 注册事件
    onMounted(() => {
        window.addEventListener('click', savePoint)
    })
​
    // 卸载前, 取消注册的事件
    onBeforeUnmount(() => {
        window.removeEventListener('click', savePoint)
    })
​
    return point
}
<template>
  <p>求和: {{sum}}</p>
  <button @click="sum++">点击加1</button>
  <hr>
  <h2>获取鼠标坐标点: ({{point.x}}, {{point.y}})</h2>
</template>
​
<script>
import { ref } from 'vue';
import savePoint from '../hooks/savePoint'
​
export default {
  name: 'App',
  setup() {
    let sum = ref(0);
    const point = savePoint();
    
    return {
      sum,
      point
    }
  },
  
}
</script>
​
<style>
</style>

11. toRef

  • 作用: 创建一个ref对象, 其value值指向另一个对象中的某个属性

  • 语法: const name = toRef(person, 'name')

  • 应用: 将响应式对象中的某个属性单独提供给外部使用(也变成响应式的)

  • toRefs: 可以批量创建多个ref对象, toRefs(person)

<template>
  <h5>{{person}}</h5>
  <p>姓名: {{name}}</p>
  <p>年龄: {{age}}</p>
  <h4>薪水: {{job.j.salary}}K</h4>
  <hr>
  <button @click="name += '~'">改姓名</button>
  <button @click="age ++">改年龄</button>
  <button @click="job.j.salary ++">涨薪</button>
</template>
​
<script>
import { reactive, toRefs } from 'vue';
​
export default {
  name: 'App',
  setup() {
    let person = reactive({
      name: 'Tom',
      age: 18,
      job: {
        j: {
          salary: 20
        }
      }
    })
​
    // toRef 单个变为ref对象
    // let name = toRef(person, 'name');
    // let age = toRef(person, 'age');
    // let salary = toRef(person.job.j, 'salary');
    // console.log(salary);
​
    // toRefs 可将整个对象里的所有属性都变成ref对象
    let p = toRefs(person);
    console.log(p.job.value.j.salary);// 20
​
​
    return {
      person,
      ...p
    }
  }
}
</script>
​
<style>
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值