js---vue3

154 阅读5分钟

vue3

1.使用vue-cli创建vue3工程
  • 查看vue-cli 版本, vue -V,vue-cli 4.5.0 以上
  • 安装最新的vue-cli, npm i -g @vue/cli --force (强制覆盖之前版本)
  • vue create 项目名
  • 关闭eslint , 根目录添加vue.config.js 【与package.json文件同级】
module.exports = {
       lintOnSave: false
}
  • 安装vue开发工具,谷歌应用商店或在线下载, vue(beta), 目前不支持vuex
2.vue3 实现响应式 VS vue2 实现响应式
2.1 vue2
/**
* 模拟vue2  defineProperty
* 
*/
const data = { name: "bwf", age: 18, }
const _data = {}
function init(data) {
for (const key of Object.keys(data)) {
  Object.defineProperty(_data, key, {
    configurable: true,
    //读取属性会执行的回调
    get() {
      console.log('get');
      return data[key]
    },
    //修改属性会执行的回调
    set(newValue) {
      console.log('set', newValue);
      data[key] = newValue
    }
  })

 }
}
init(data)
2.2 vue3
/**
* 模拟vue3 Proxy代理
* target: 被代理的对象 data
* properName: 操作的属性名
* value:  值
* 
*/
const p = new Proxy(data, {
//读取属性会执行的回调
get(target, properName) {
  console.log('get', target, properName);
  return target[properName]
},
//修改或添加属性会执行的回调
set(target, properName, value) {
  console.log('set', target, properName, value);
  target[properName] = value
},
//删除属性会执行的回调
deleteProperty(target, properName) {
  console.log('deleteProperty', target, properName);
  return delete target[properName]
}
})
2.3 vue3 vs vue2
 /**
   * vue2
   * 代理--->>> Object.defineProperty  让属性拥有get set监听回调函数
   * 缺点:  
   *     只能对对象已有属性实现监听, 添加属性/删除属性  无法做到监听;vue里面的体现就是数据改变了,但是页面不是响应式的
   *     利用索引直接设置一个数组项时 或者 修改数组的长度时, 页面不是响应式的
   *       如 this.list[0] = {name:"bwf"} 或者  this.list.length = 3
   * 对此vue2的解决办法是
   *     【对象添加属性】
   *        解决方式1: this.info = Object.assign({}, this.info, { age: 999, job: { salary: "9k" } })  属性age和job 拥有了get/set 都是响应式的
   *        解决方式2: this.$set(this.info, "age", 100)    属性age 拥有了get/set 是响应式的
   *     【对象删除属性】
   *        解决方式: this.$delete(this.info, "name")
   *     【数组】
   *       解决方式1: this.$set(this.list, 0, { name: "xxxx" })
   *       解决方式2: 运用vue重写的数组的方法(既实现了对原数组的操作,又会引起页面的渲染) splice / push / pop / shift / unshift / sort / reverse 
   * 
   * vue3
   * 代理--->>>new Proxy 
   * 优势: 
   *    1.不用再像vue2中遍历data中的每个属性然后添加 get/set 函数
   *    2.能捕获对数据的 增/删/改/查   
  */
3. vue3中常见api
3.1 template
template:  v-指令 /  插槽 /  模板语法 /  事件绑定 /  父子属性传值 和 vue2一样
3.2 setup
[1]本质是一个函数 返回出去的数据以及方法才能被页面获取到
[2]执行时机: 在beforeCreate之前执行1次(此时thisundefined); 注意props改变也不再执行
[3]接收到的参数:
      props 父组件传过来的属性数据,值得注意的是子组件最好也要有props配置项(写法同vue2)
     context {emit: 触发父事件, slots: 父组件传过来的插槽}
3.3 响应式函数
ref(): 一般用于处理基本类型数据,除模板外,任何读写的地方都要 .value
reactive():处理引用类型数据,删除添加对象的属性 以及 直接改变数组某一项都是响应式的,不用.value
3.4 常见的生命周期函数 【兼容vue2写法】
onMounted: dom渲染完成       onBeforeUnmount:页面卸载之前
3.5 computed 计算属性
setup里面可以注册多个computed  【兼容vue2写法,写在配置项里面】
执行时机: 
      前提是计算属性有放到 template;
	  第一次执行 在created钩子之后,mounted钩子之前触发 ; 
	  后面每次执行是只要是 computed里面的响应式的数据发生改变就会执行;
3.6 watch 侦听属性
//setup里面可以注册多个watch【兼容vue2写法,写在配置项里面】
/**
 * 可以开启多组监听
 * 
 */
watch([sum, msg], (newValue, oldValue) => {
  console.log('watch---sum', newValue, oldValue)  // [sum, msg]
}, { immediate: true })

     /**
 * 监听reactive定义的对象的全部属性  自动 deep:true 而且oldValue有误
 * 如下 监视 person对象任何属性改变,都会执行 watchEffect的回调
 */
watch(person, (newValue, oldValue) => {
  // console.log('watch---person', newValue, oldValue)
}, { deep: false })
    /**
 *  监听reactive定义的对象中某个属性
 *  如下 只监视 person对象的name属性,然后做一些逻辑处理
 */
watch(() => person.name, (newValue, oldValue) => {
  // console.log('watch---person.name', newValue, oldValue)
})

3.7 watchEffect
类似computed, 但不接收返回值 
执行时机: 
  前提是计算属性有放到 template 
      第一次执行 在created钩子之后,mounted钩子之前触发 ; 
      后面每次执行是只要是 watchEffect 里面的响应式的数据发生改变就会执行;
/**
 *  自动 immediate:true  类似computed, 但不接收返回值
 *  watchEffect用到响应的某个属性 就监视那个属性,并读取最新的值
 *  如下 person.name, person.age 发生改变都会执行watchEffect的回调
 */
watchEffect(() => {
  console.log('watchEffect', person.name, person.age);

})
3.8 provide / inject 父代向后代传递数据 / 接收数据
let job = reactive(
{ salary: 9, address: "上海" }
)
 /**
* 祖组件向后代组件传递数据
*/
provide('appData', job)

 /**
* 后代组件接收数据
*/
const appData = inject('appData');
4.演示demo
  • App是祖先组件,主要演示setup、ref()、reactive()、生命周期函数、provide、向ComputedDemo子组件传递数据和事件
  • ComputedDemo组件演示computed、接收App父组件传递的数据和调用父组件身上注册的事件
  • WatchDemo组件演示watch、watchEffect
  • ChildChild组件演示inject
4.1 App组件
<template>
  <div class="app-page">
    <div>
      <h3>App page</h3>
      <p>年龄{{age}}</p>
      <button @click="addAge">点我年龄 + 1</button>
      <p>工作信息:地点:{{job.address}}, <span style="color:red;font-weight:700">薪资:{{job.salary}}k</span> </p>
      <button @click="addSalary">点我薪资+1</button>
      <p>属性sex:{{job.sex}}</p>
      <button @click="addSexPropery">添加/切换属性sex</button>
      <ul>
        <li v-for="(item,index) in list" :key="index">
          {{item.name}}
        </li>
      </ul>
      <button @click="editList">点我修改数组第1项</button>
      <p>ComputedDemo page子组件给父组件传过来的 <span style="color:red;font-weight:700">name:{{name}}</span></p>
    </div>
    <computed-demo :job="job" @handleName="handleName">
      <!-- 以下展示的是 父组件给子组件传入节点dom -->
      <template v-slot:slot1>
        <span>父组件传过来的----node1</span>
      </template>
      <template v-slot:slot2>
        <span>父组件传过来的----node2</span>
      </template>
    </computed-demo>
    <watch-demo></watch-demo>
  </div>
</template>

<script>
import ComputedDemo from './components/ComputedDemo'
import WatchDemo from './components/WatchDemo'
import { ref, reactive, onMounted, onBeforeUnmount, computed, provide } from 'vue'
export default {
  name: 'App',
  components: {
    ComputedDemo,
    WatchDemo
  },
  beforeCreate () {
    console.log('beforeCreate')
  },
  setup (props, context) {
    console.log("App---setup", props)


    let age = ref(90);
    let name = ref("");
    let job = reactive(
      { salary: 9, address: "上海" }
    )
    const list = reactive([{ name: "bwf" }, { name: "wmy" }])

    /**
     * 向后代组件传递数据
     */
    provide('appData', job)
    onMounted(() => {
      console.log("app onMounted")
    })

    const addAge = () => {
      age.value++
    }
    const addSalary = () => {
      job.salary++
    }
    const handleName = (payloads) => {
      console.log("父组件定义的方法被子组件emit方式触发了,子组件传过来的数据是", payloads)
      name.value = payloads.value
    }
    const editList = () => {
      list[0] = { name: "xxxs" }
    }
    const addSexPropery = () => {
      if (job.sex) {
        delete job.sex
      } else {
        job.sex = "男"
      }
    }
    return {
      age, job, addAge, addSalary, name, list, handleName, addSexPropery, editList
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
.app-page {
  display: flex;
  background-color: yellow;
}
</style>

4.2 ComputedDemo组件
<template>
  <div class="computed-page">
    <h3>ComputedDemo page</h3>
    <p>父组件传过来的<span style="color:red;font-weight:700">薪资:{{ job.salary }}k</span></p>
    <button @click="job.salary++">尝试修改父组件传过来的salary</button><br>
    <slot name="slot1"></slot>
    <p>哈哈哈</p>
    <slot name="slot2"></slot>
    <br>
    <input type="text" v-model="firstName">
    <input type="text" v-model="lastName">
    <p>全名:{{fullName}}</p>
  </div>
</template>

<script>
import { ref, reactive, computed, watch, readonly } from 'vue'

export default {
  name: 'ComputedDemo',
  props: {
    job: Object
  },
  created () {
    console.log("created")
  },
  mounted () {
    console.log("mounted")

  },
  setup (props, context) {
    console.log("ComputedDemo---setup", props.job)
    readonly(props.job)
    const name = ref("bwf")
    context.emit("handleName", name)

    const firstName = ref("")
    const lastName = ref("")

    const fullName = computed(() => {
      console.log('computed', firstName.value);
      return firstName.value + '---' + lastName.value;
    })


    return {
      firstName, lastName, fullName
    }
  }
}
</script>

<style scoped>
.computed-page {
  background-color: palegreen;
}
</style>

4.3 WatchDemo组件
<template>
  <div class="watch-page">
    <h3>WatchDemo Page</h3>
    <button @click="sum++">点我sum++</button>
    <p>msg:{{msg}}</p>
    <button @click="msg+='@'">点我msg++</button>
    <p>person---name:{{name}}---age:{{age}}---salary:{{job.salary}}</p>
    <button @click="age++">点我修改person.age</button>
    <button @click="job.salary++">点我修改person.job.salary</button>
    <button @click="name+='@'">点我修改person.name</button>
    <child-child></child-child>
  </div>
</template>

<script>
import { ref, reactive, computed, watch, toRefs, watchEffect } from 'vue'
import ChildChild from './ChildChild'
export default {
  name: "WatchDemo",
  components: {
    ChildChild
  },
  setup (props) {
    console.log("WatchDemo---setup")
    let sum = ref(0);
    let msg = ref("你好");

    let person = reactive({
      name: "bwf",
      age: 16,
      job: {
        salary: 9
      }
    })


    /**
     * 可以开启多组监听
     * 
     */
    watch([sum, msg], (newValue, oldValue) => {
      console.log('watch---sum', newValue, oldValue)  // [sum, msg]
    }, { immediate: true })

    /**
     * 监听reactive定义的对象的全部属性  自动 deep:true 而且oldValue有误
     * 如下 监视 person对象任何属性改变,都会执行 watchEffect的回调
     */
    watch(person, (newValue, oldValue) => {
      // console.log('watch---person', newValue, oldValue)
    }, { deep: false })

    /**
     *  监听reactive定义的对象中某个属性
     *  如下 只监视 person对象的name属性,然后做一些逻辑处理
     */
    watch(() => person.name, (newValue, oldValue) => {
      // console.log('watch---person.name', newValue, oldValue)
    })

    /**
     *  自动 immediate:true  类似computed, 但不接收返回值
     *  watchEffect用到响应的某个属性 就监视那个属性,并读取最新的值
     *  如下 person.name, person.age 发生改变都会执行watchEffect的回调
     */
    watchEffect(() => {
      console.log('watchEffect', person.name, person.age);

    })

    return {
      sum,
      msg,
      ...toRefs(person)
    }
  }
};
</script>

<style scoped>
.watch-page {
  background-color: pink;
}
</style>

4.4 ChildChild组件
<template>
  <div>
    <h4>ChildChild page</h4>
    <div>app祖父组件通过provide传递过来数据,
      <p>工作信息:地点:{{appData.address}}, <span style="color:red;font-weight:700">薪资:{{appData.salary}}k</span> </p>
    </div>
  </div>
</template>

<script>
import { inject } from 'vue'
export default {
  name: "ChildChild",
  setup (props) {
    console.log('ChildChild---setup');
    const appData = inject('appData');
    console.log('app祖父组件通过provide传递过来数据', appData);
    return {
      appData
    }
  }
};
</script>

<style scoped>
</style>