vue3基础学习

vue3  六大亮点:
Performance:性能比vue2快1.2~2倍。
Tree shaking support:按需编译,体积比vue2更小
Composition API:组合API
BetterTypescript support:更好的TS支持
Custom Renderer API:暴露了自定义渲染API
Fragment,Teleport(portal),Suspense:更先进的组件。

vue3如何变快的?
diff算法优化:
Vue2中的虚拟算法dom是进行全量的对比。
vue3新增了静态标记(patchFlag);
与上一次虚拟节点进行对比时候,之对比带有patch flag的节点
并且可以通过falg的信息得知当前节点要对比的具体内容
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("span", null, "hello"),
    _createVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
  ]))
}
Vue3.0 diff 在创建虚拟Dom的时候会根据Dom中内容会不会发生变化,添加静态标记。

hoistStatic 静态提升;
Vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染。
vue3中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "hello", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _hoisted_1,
    _createVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
  ]))
}

cacheHandlers 事件侦听器缓存;
默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化
但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", { onClick: _ctx.btnClick }, null, 8 /* PROPS */, ["onClick"])
  ]))
}

ssr渲染:
当有大量静态的内容时候,这些内容会被当作纯字符串推进一个buffer里面。
即使存在动态的绑定,会通过模版插入值嵌入进去。这样会比通过虚拟dom来渲染的快的多
当静态内容大到一定量级时候,会用_createStaticVNode方法在客户端去生成一个static node
这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。

vue-cli 4
Vite : 实现原理 利用ES6的import 会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去了webpack冗长的打包时间。
npm install -g create-vite-app
create-vite-app projectName
cd projectName
npm run dev

API:
createApp
setup()
reactive()

filter()

this.objArr = this.objArrs.filter((item,ind)=>item.name)
const obj1 = Object.assign({},obj)

const minN = (...arr, n = 0) => arr.sort((a, b) => a - b).slice(0, n)

Vue.createApp({

})

<template>
  <div>
     {{ count }}<button @click="addCount">sub</button>
     <div>
       <input v-model="inputVal" @click.enter="handleInputVal" />
     </div>
     <div v-for="(item,ind) in state.stus" :key="item.id" @click="removeStu(ind)" >{{ind}}--{{item.id}}--{{item.name}}</div>
     <form>
       <input type="number" v-model="state2.stu.id" /><input type="text" v-model="state2.stu.name" /><input type="submit" @click="addStu" />
     </form>
  </div>
</template>
<script>
import { ref, reactive } from 'vue'
import { useAddStu } from './addStu';
export default {
  name: 'Index',
  setup() { // setup函数是组合API的入口函数
    // 定义了一个名称叫做count变量,这个变量的初始值是0
    // 这个变量发生改变之后,Vue会自动更新UI
    // ref()函数注意点:
    // ref函数只能监听简单类型的变化,不能监听复杂类型的变化(对象/数组)
    let count = ref(0);
    // reactive()
    let state = reactive({
      stus: [
        { id: 1, name: 'Jack' },
        { id: 2, name: 'Tome' }
      ]
    })
    // 在组合API 中,如果想定义方法,不用定义到methods中,直接定义即可,return 暴露出去
    function addCount() {
      console.log(count) // count是一个对象
      count.value++;
    }
    function removeStu(index) {
      state.stus = state.stus.filter((item, ind) => ind !== index)
    }
    // 写法1:
    // let inputVal = ref('kobe')
    // function handleInputVal() {
    //   let id = state.stus[state.stus.length - 1].id
    //   state.stus.push({ id, name: inputVal })
    // }
    //抽离 state, removeStu() 写法
    // let {state, removeStu} = useRemoveStu();
    let { inputVal, handleInputVal } = useAddStu(state);

    //注意点:
    // 在组合API中定义的变量/方法,要想在外界使用,必须通过return { xxx, xxx } 暴露出去
    return { count, addCount, state, removeStu, inputVal, handleInputVal, }
  }

}
// 抽离出来  解决数据与业务逻辑分散的问题
function useRemoveStu() {
  let state = reactive({
    stus: [
      { id: 1, name: 'Jack' },
      { id: 2, name: 'Tome' }
    ]
  })
  // 点击删除该项
  function removeStu(index) {
    state.stus = state.stus.filter((item, ind) => ind !== index)
  }
  return { state, removeStu }
}
/* 写法2: 抽离为一个单独的函数;  写法3: 或者可以抽离为一个单独的文件
function useAddStu(state) {
  let inputVal = ref('kobe')
  function handleInputVal() {
    let id = state.stus[state.stus.length - 1].id
    state.stus.push({ id, name: inputVal })
  }
}
*/

</script>

import {
  ref,
  reactive
} from 'vue'

function useAddStu(state) {
  let inputVal = ref('kobe');

  function handleInputVal() {
    let id = state.stus[state.stus.length - 1].id
    state.stus.push({
      id,
      name: inputVal
    })
  }
  return {
    inputVal,
    handleInputVal
  }
}

function useAddStudent(state) {
  let id = state.stus[state.stus.length - 1].id
  let state2 = reactive({
    stu: {
      id,
      name: ''
    }
  })

  function addStudent(state) {
    e.preventDefault()
    const stu = Object.assign({}, state2.stu)
    state.stus.push(stu)
  }
  return {
    state2,
    addStudent
  }
}
export {
  useAddStu,
  useAddStudent
}

setup:

<template>
  <div>
     <span>{{name}}</span><button @click="handleFnOne">按钮1</button>
     <span>{{sex}}</span><button @click="handleFnTwo">按钮2</button>
  </div>
</template>
<script>
/*
1.Composition API 和 Option API 混用
2.Composition API 本质(组合API / 注入API)
3.setup 执行时机
beforeCreate : 表示组建刚刚被创建出来,组件的data和methods还没有初始化完成
setup  
created : 表示组建刚刚被创建出来,并且组件的data和methods已经初始化完成
4.setup注意点:
1.setup是在beforeCreate和Created之间执行。
由于在执行setup函数的时候,还没有执行Created生命周期方法,所以在setup函数中,是无法使用data和methods
setup函数中是不能够使用data和methods的
2.由于我们不能在setup函数中使用data和methods,所以Vue为了避免我们错误的使用,它直接将setup函数中this修改成了undefined
3.setup函数只能是同步的不能是异步的
 */
import { ref, reactive } from 'vue'

export default {
  name: 'Demo2',
  data() {
    return {
      name: 'kobe'
      // sex:'man'
    }
  },
  methods: {
    handleFnOne() {
      console.log(this.name)
    }
    // handleFnTwo() {
    //   console.log(this.sex)  // 本质 运行时将Composition API 暴露出来的数据注入到 Option API 中
    // }
  },
  setup() { // setup函数是组合API的入口函数
    let sex = ref('man')
    function handleFnTwo() {
      console.log(sex.value)
    }
    console.log(this) // undefined
    console.log(this.name) // 报错
    return { count, handleFnTwo }
  }
}
// 测试点 composition和option中是否可以相互改变对方数据。
</script>

reactive:

<template>
  <div>
     <p>{{state.time}}</p>
     <button @click="handleClick">按钮</button>
  </div>
</template>
<script>
/*
1.什么是reactive?
reactive是VUE3中提供的实现响应式数据的方法
在Vue2中响应式数据是通过defineProperty来实现的
而在Vue3中响应式数据是通过ES6的proxy 来实现的
2.reactive 注意点:
reactive参数必须是对象(json/arr)
如果给reactive传递了其他对象,默认情况下修改对象,界面不会自动更新, 如果想更新,可以通过重新赋值的方式
 */
import { ref, reactive } from 'vue'

export default {
  name: 'Demo2',
  setup() { // setup函数是组合API的入口函数
    let sex = ref('man')
    // let state=reactive(123) // 由于在创建响应式数据的时候传递的不是一个对象,所以无法是响应式。
    // let state = reactive([1,3,5]) // state[0]=666 可以实现响应式
    let state = reactive({
      time: new Date()
    })
    function handleClick() {
      // state = 666; // 传入的不是对象时,state值为123
      // state[0] = 666
      // 直接修改以前的,界面不会更新
      // state.time.setDate(state.time.getDate()+1)
      // 重新赋值
      const newTime = new Date(state.time.getTime())
      newTime.setDate(state.time.getDate() + 1)
      state.time = newTime
      console.log(state.time)
      console.log(state)
    }
    console.log(this) // undefined
    return { state, handleClick }
  }
}
</script>

ref:

<template>
  <div>
    <!-- 如果是通过ref创建的数据,那么在template中使用的时候不用通过 .value来获取,因为Vue会自动给我们添加 .value -->
    <!-- ref和reactive的区别:
    如果在template里使用的是ref类型的数据,那么Vue会自动帮我们添加.value
    如果在template里使用的是reactive类型的数据,那么Vue不会自动帮我们添加.value
    Vue是如何决定是否需要自动添加。value的, vue在解析数据之前,会自动判断这个数据是否是ref类型的,如果是就自动添加.value,如果不是就不自动添加.value
    vue是如何怕断当前的数据是否是ref类型的
    通过当前数据的__v_ref 来判断的
    如果有这个私有的属性,并且取值为true,那么就代表是一个ref类型的数据 -->
     <p>{{state}}</p>
     <button @click="handleClick">按钮</button>
  </div>
</template>
<script>
/*
1.什么是ref?
ref 和 reactive一样,也是用来实现响应式数据的方法
由于reactive必须传递一个对象,所以导致在企业开发中,如果我们只想让某个变量实现响应式的时候会非常麻烦,所以Vue3就给我们提供了ref方法,实现对简单值的监听
2.ref本质:
ref底层的本质其实还是reactive
系统会自动根据我们给reef传入的值将它转换成 ref(xx) -> reactive({value:xx})
3.ref 注意点:
在Vue中使用ref的值不用通过value获取
在JS中使用ref的值必须通过value获取
 */
import { ref, reactive,isRef,isReactive } from 'vue'

export default {
  setup() { // setup函数是组合API的入口函数
    let state = ref('man')
    let state=reactive({value:'man'})
    // ref本质: ref本质其实还是reactive,当我们给ref函数传递一个值之后,ref函数底层会自动将ref转换成reactive  
    function handleClick() {
      state.value = 'woman'
      console.log(state)
      console.log(isRef(state))
      console.log(isReactive(state))
    }
    console.log(this) // undefined
    return { state, handleClick }
  }
}
</script>

shallowRef:

<template>
  <div>
     <p>{{state}}</p>
     <button @click="handleClick">按钮</button>
  </div>
</template>
<script>
/*
1.递归监听
默认情况下,无论是通过ref还是reactive都是递归监听
2.递归监听存在的问题
如果数据量比较大,非常消耗性能, 每一层都包装成一个proxy对象
3.非递归监听
只能监听第一层,只把第一层包装成一个proxy对象,如果深层数据第一层数据不改变,改变其他层数据,数据不更新。
注意点:
shallowRef : 如果是通过shallowRef创建数据,那么Vue监听的是 .value 的变化, 并不是第一层的变化。
4.应用场景
一般情况下我们使用ref和reactive 即可,
只有在需要监听的数据量比较大的时候,我们才使用shallowref/ shallowReactive

shallowRef本质:
shallowRef  ->  shallowReactive
shallowRef(10) -> shallowReactive({value:10})
所以如果是通过shallowRef 创建的数据,它监听的是 .value的变化,因为底层本质上value才是第一层。

toRaw:
从Reactive 或 ref 中得到原始数据 
toRaw作用
做一些不想被监听的事情(提升性能)

 */
import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw } from 'vue'

export default {
  setup() { // setup函数是组合API的入口函数
    // let state=ref({b:{
    //       c:{
    //         d:{
    //           e:'e1'
    //         }
    //     }}})
    let state = reactive({
      a: 'man',
      b: {
        c: {
          d: {
            e: 'e1'
          }
        }
      }
    })
    function handleClick() {
      state.b.c.d.e = 'woman'
      // state.value.b.c.d.e = 'e2'
      //triggerRef:
      state.value.b.c.d.e = 'e3'
      // Vue只提供了triggerRef方法,没有提供triggerReactive 方法,所以如果是reactive 类型的数据,那么是无法主动触发界面更新。 
      triggerRef(state)
    }

    /* 
    ref / reactive 数据类型的特点:
    每次修改都会被追踪,都会更新UI 界面,但是这样其实是非常消耗性能的,所以如果我们有一些操作不需要追踪,不需要更新UI界面,那么这个时候,
    我们可以通过toRaw 方法拿到它的原始数据,对原始数据进行修改,这样就不会被追踪,这样就不会更新UI界面,这样性能就好了
    */
    let obj = { name: 'kobe', age: 18 }
    let state = reactive(obj)
    console.log(state === obj) // false

    let obj2 = toRaw(state)
    console.log(obj2 === obj) // true
    // state和obj 的关系:
    // 引用关系,state的本质是一个Proxy对象, 在这个Proxy对象中引用了 obj
    function myFn() {
      // 如果直接修改obj, 那么是无法触发界面更新的
      // 只有通过包装之后的对象来修改,才会触发界面的更新
      obj.name = 'zs'
      console.log(obj) // {name:'zs',age:18}
      console.log(state) // {name:'zs',age:18}

      obj2.name = 'zs'
      console.log(obj2)
      console.log(state)

      // state.name='zs'
      // console.log(state)

    }

    // ref本质:reactive  ref(obj) -> eactive({value:obj})
    // 注意点:如果想通过toRaw 拿到ref类型的原始数据(创建时传入的那个数据),
    // 那么就必须明确的告诉toRaw 方法,要获取的是 .value的值, 因为经过Vue处理之后吗.value中保存的才是当初创建时传入的那个原始数据
    let obj={name: 'kobe', age: 18}
    let state=ref(obj)
    let obj2=toRaw(state.value)
    console.log(obj)
    console.log(state) // 
    console.log(obj2)


    console.log(this) // undefined
    return { state, handleClick }
  }
}
</script>

markRaw:

<template>
  <div>
     <p>{{state}}</p>
     <button @click="handleClick">按钮</button>
  </div>
</template>
<script>
/*
1.markRaw 解除数据跟踪响应式

 */
import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw, markRaw, toRef } from 'vue'

export default {
  setup() { // setup函数是组合API的入口函数
    let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
    obj = markRaw(obj) // 解除跟踪
    let state = reactive(obj)
    function handleClick() {
      state.name = 'james' // 不会改变
    }

    let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
    obj = markRaw(obj) // 解除跟踪
    let state = ref(obj.name)
    function handleClick() {
      state.name = 'james' // 不会改变
    }

    console.log(this) // undefined
    return { state, handleClick }
  }
}
</script>

toRef / toRefs:

<template>
  <div>
     <p>{{state}}</p>
     <button @click="handleClick">按钮</button>
  </div>
</template>
<script>
/*
1.markRaw 解除数据跟踪响应式
2.ref 和 toRef 的区别:
ref -> 复制,修改响应式数据不会影响以前的原始数据
toRef -> 引用, 修改响应式数据会影响到以前的原始数据
ref -> 数据发生改变,UI界面就会自动更新
toRef -> 数据发生改变,UI界面也不会自动更新

toRef 应用场景:
如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新UI,那么就可以使用toRef
 */
import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw, markRaw, toRef, toRefs } from 'vue'

export default {
  setup() { // setup函数是组合API的入口函数
    let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
    obj = markRaw(obj) // 解除跟踪
    let state = reactive(obj)
    function handleClick() {
      state.name = 'james' // 不会改变
    }

    let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
    obj = markRaw(obj) // 解除跟踪
    let state = ref(obj.name)
    function handleClick() {
      state.value = 'james' // 不会改变
    }

    let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
    // ref -> 复制
    let state = ref(obj.name)
    function handleClick() {
      state.value = 'james' // 
      /**
       * 结论: 如果利用ref将某一个对象中的属性变成响应式的数据
       * 我们修改响应式的数据是不会影响到原始数据的,本质是将原始数据中的name取出来了,然后复制
       */
      console.log(obj, state) // 原始obj不会变,state改变
    }

    let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
    // toRef -> 引用
    let state = toRef(obj, 'name')
    function handleClick() {
      state.value = 'james' // 
      /**
       * 结论: 如果利用toRef将某一个对象中的属性变成响应式的数据
       * 我们修改响应式的数据是会影响到原始数据的,但是如果响应式的数据是通过toRef 创建的,那么修改了数据并不会触发UI界面的更新
       */
      console.log(obj, state) // 原始数据obj,state均会改变
    }

    let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
    // toRefs -> 多个属性都要变成响应式时
    let state = toRefs(obj)
    function handleClick() {
      state.name.value = 'james'
      state.address = 'beijing'

      console.log(obj, state) // 原始数据obj,state均会改变
    }

    console.log(this) // undefined
    return { state, handleClick }
  }
}
</script>

customRef:

<template>
  <div>
     <p>{{state}}</p>
     <button @click="handleClick">按钮</button>
  </div>
</template>
<script>
/**
 * 1.customRef  自定义一个ref
 * 返回一个ref 对象,可以显式的控制依赖追踪和触发响应
 * 
 */

import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw, markRaw, toRef, toRefs, customRef } from 'vue'

function useDebouncedRef(value, delay = 200) {
  let timer
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

function myRef() {
  return customRef((track, trigger) => {
    return {
      get() {
        track() // 追踪数据变化
        console.log('get', value)
        return value
      },
      set(newValue) {
        value = newValue
        console.log('set', newValue)
        trigger() // 触发界面更新
      }
    }
  })
}

function myRef1(val) {
  return customRef((track, trigger) => {
    fetch(val)
      .then(res => res.json())
      .then(data => {
        console.data
        value = data
        trigger()
      })
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        value = newValue
        trigger()
      }
    }
  })
}

export default {
  setup() { // setup函数是组合API的入口函数
    // let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
    // let state = myRef(obj.name)
    // function handleClick() {
    //   state.value = 'james'
    //   console.log(obj, state) // 
    // }

    // let state=ref([])
    // fetch('./public/data.json')
    //   .then((res)=>{
    //     return res.json()
    //   }).then(data=>{
    //     console.log(data)
    //     state.value=data
    //   }).catch(err=>{
    //     console.log(err)
    //   })

    let state = myRef1('./public/data.json')

    console.log(this) // undefined
    return { state, handleClick }
  }
}
</script>

re获取元素:

<template>
  <div>
     <p ref="box">{{state}}</p>
     <button @click="handleClick">按钮</button>
  </div>
</template>
<script>
/**
 * 1.获取元素
 * 在Vue2.x 中我们可以通过给元素添加ref='xxx', 然后再代码中通过refs.xxx的方式来获取元素
 * 在Vue3.x 中我们也可以通过ref 来获取元素
 */

import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw, markRaw, toRef, toRefs, customRef, onMounted, readonly, isReadonly, shallowReadonly } from 'vue'

export default {
  setup() { // setup函数是组合API的入口函数
    // let box = ref(null)
    // onMounted(()=>{
    //   console.log('onMounted',box.value) // p块
    // })
    // console.log(box.value) // null

    // readonly : 用于创建一个只读的数据,并且是递归只读
    /**
     * const 和 readonly 区别:
     *  const : 赋值保护,不能给变量重新赋值
     * readonly: 属性保护,不能给属性重新赋值
     * */ 
    let state = readonly({
      name: 'kobe', attr: { address: 'china' }
    })
    function handleClick() {
      state.name = 'james' // 只读属性,值不会变化
      state.attr.address = 'beijing' // 只读属性,值不会变化
      console.log(state)
      console.log(isReadonly(state))  // true
    }

    // shallowReadonly : 用于创建一个只读的数据,但是不是递归只读的
    let state = shallowReadonly({
      name: 'kobe', attr: { address: 'china' }
    })
    function handleClick() {
      state.name = 'james' // 第一层只读属性,第一层值不会变化
      state.attr.address = 'beijing' // 只值发生改变,UI不会同步更新
      console.log(state)
      console.log(isReadonly(state))  // true
    }

    console.log(this) // undefined
    return { box, handleClick }
  }
}
</script>

vue3响应式:

<template>
  <div>
     <p ref="box">{{state}}</p>
     <button @click="handleClick">按钮</button>
  </div>
</template>
<script>
/**
 * 1.在Vue3 响应式数据本质
 * 在Vue2.x 中是通过defineProperty来实现响应式数据的
 * 在Vue3.x 中是通过proxy来实现响应式数据的
 */

let obj = { name: 'kobe', address: 'los' }
let arr = [1, 3, 5, 7]
let state = new Proxy(obj, {
  get(obj, key) {
    console.log('obj', obj, key) // {name:'kobe',address:'los'} name
    return obj[key]
  },
  set(obj, key, value) {
    console.log(value, key, value)
    obj[key] = value
    console.log('更新Ui界面')
    return true // 注意点 为何要return true
  }
})
console.log(state.name) // kobe
state.name = 'james'
console.log(state)

/**
 * shallowReactive, shallowRef
 */
function shallowReactive(obj) {
  return new Proxy(obj, {
    get(obj, key) {
      console.log(obj, key)
      return obj[key]
    },
    set(obj, key, value) {
      console.log(obj, key, value)
      obj[key] = value
      console.log('更新UI界面')
      return true
    }
  })
}
function shallowRef(val) {
  return shallowReactive({ value: val })
}
let obj = { a: 'a', b: { c: 'c' } }
let state = shallowReactive(obj)
state.a = '1'
state.b.c = '3'
let state = shallowRef(obj)
state.value = { a: '1', b: { c: '3' } }

/**
 * reactive, ref
 */
function reactive(obj) {
  if (typeof obj === 'object') {
    if (obj instanceof Array) {
      // 如果是一个数组,那么取出数组中的每一个元素,
      //判断每一个元素是否又是一个对象,如果又是一个对象,那么也需要包装成Proxy
      obj.forEach((item, index) => {
        if (typeof item === 'object') {
          obj[index] = reactive(item)
        }
      })
    } else {
      // 如果是一个对象,那么取出对象属性的值,
      //判断对象属性的取值是否又是一个对象,如果又是一个对象,那么也需要包装成Proxy
      for (let key in obj) {
        let item = obj[key]
        if (typeof obj[key] === 'object') {
          obj[key] = reactive(item)
        }
      }
    }
    return new Proxy(obj, {
      get(obj, key) {
        console.log(obj, key)
        return obj[key]
      },
      set(obj, key, value) {
        console.log(obj, key, value)
        obj[key] = value
        console.log('更新UI界面')
        return true
      }
    })
  } else {
    console.warn('not object')
    return
  }
}
let obj = { a: 'a', b: { c: 'c' } }
let state = reactive(obj)
let arr = [{ a: 'a' }, { b: 'b' }]
let state = reactive(arr)

function ref(val) {
  return reactive({ value: val })
}

/**
 * shallowReadonly, readonly
 */

function shallowReadonly(obj) {
  return new Proxy(obj, {
    get(obj, key) {
      console.log(obj, key)
      return obj[key]
    },
    set(obj, key, value) {
      // console.log(obj, key, value)
      // obj[key] = value
      console.log(key+'只读的,不能赋值')
      // return true
    }
  })
}
let obj={a:'a'}
let state = shallowReadonly(obj)

import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw, markRaw, toRef, toRefs, customRef, onMounted, readonly, isReadonly, shallowReadonly } from 'vue'

export default {
  setup() { // setup函数是组合API的入口函数
    let state = ref({
    })
    function handleClick() {
      console.log(state)
    }

    console.log(this) // undefined
    return { box, handleClick }
  }
}
</script>
支持ts,proxy代替defineProperty,重写虚拟DOM的实现和Tree Shaking, Composition(组合)API 
setup:
setup函数在beforeCreate之后,created之前执行。setup()函数中无法访问到this!!!!
接收props数据:
在props中定义当前组件允许外界传递过来的参数名称:props:{p1:String}
通过setup函数的第一形参,接收props数据:setup(props){console.log(props.p1)}
context:
setup函数的第二个形参是一个上下文对象,这个上下文对象中包含了一些有用的属性,这些属性在vue2.x 中需要通过this才能访问到,在vue3.x中,它们的访问方式:
const MyComponent={
  setup(props,context){
    context.attrs
    context.slots
    context.parent
    context.root
    context.emit
    context.refs
  }
}
reactive:
reactive()函数接收一个普通对象,返回一个响应式的数据对象。
等价于vue2.x中的Vue.observable()函数,Vue3.x中提供了reactive()函数,用来创建响应式的数据对象
import {reactive, ref, isRef, toRef, computed, watch, onMounted, onUpdated} from '@vue/composition-api'
//创建响应式数据对象,等到的state类似于vue2.x中data() 返回的响应式对象
setup(){
  const state = reactive({count:0})
  return state
}
ref:
ref函数用来根据给定的值创建一个响应式的数据对象,ref()函数调用的返回值是一个对象,这个对象上只包含一个.value属性:
setup(){
  const count =ref(0) // 创建响应式数据对象 count,初始值为0
  console.log(count.value) // 0
  count.value++
  console.log(count.value) // 1
  return {count, name: ref('zs')}
}
在reactive 对象中访问ref创建的响应式数据
当把ref()创建处理的响应式数据对象,挂载到reactive() 上时,会自动把响应式数据对象展开为原始的值,不需要通过 .value就可以直接访问
const count = ref(0)
const state = reactive({
  count
})
console.log(state.count) // 0
state.count++  // 此处不需要通过.value 就能直接访问原始值
console.log(count) // 1
注意: 新的ref 会覆盖旧的ref 
const c1=ref(0)
const state=reactive({c1})
const c2 = ref(9)
state.c1 = c2
state.c1++
console.log(state.c1) // 10
console.log(c2.value) // 10
console.log(c1.value) // 0

isRef:
isRef() 用来判断某个值是否为ref() 创建出来的对象,应用场景:当需要展开某个可能为ref() 创建出来的值的时候
const unwrapped = isRef(foo)?foo.value:foo
toRefs:
toRefs()函数可以将reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每一个属性节点,都是ref()类型的响应式数据
setup(){
  const state=reactive({count:0})
  const increment = ()=>{
    state.count++
  }
  return { ...toRefs(state), increment}
}

toRef:
const state=reactive({
  foo:1,
  bar:2
})
const fooRef = toRef(state,'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3

toRefs:
const state=reactive({
  foo:1,
  bar:2
})
const stateAsRefs=toRefs(state)
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3

function useFeatureX(){
  const state=reactive({
    foo:1,
    bar:1
  })
  return toRefs(state)
}
export default{
  setup(){
    const {foo,bar} = useFeatureX()
    return { foo, bar }
  }
}

computed:
computed()用来创建计算属性,computed()函数的返回值是一个ref 的实例。
setup(){
  const count=ref(0)
  // 根据count 的值,创建一个响应式的计算属性plusOne 
  // 它会根据依赖的ref 自动计算并返回一个新的ref 
  const plusOne=computed(()=>count.value+1) //
  console.log(plusOne.value)  // 1
  plusOne.value++  //error
  const plusTwo = computed({
    get:()=>count.value+1,
    set:(val)=>{
      count.value = val-1
    }
  })
  plusTwo.value=9  // 为计算属性赋值的操作,会触发set函数
  // 触发set函数后,count 的值会被更新
  console.log(count.value) // 8
}

watch:
const count = ref(0)
// 定义watch,只要count值变化,就会触发watch回调
// watch 会在创建时会自动调用一次
watch(()=>console.log(count.value)) // 0
setTimeout(()=>{
  count.value++ // 输出1
},1000)
const state = reactive({count:0})
watch(
  ()=>state.count,
  (count,preCount)=>{
  }
)
监视ref 类型的数据源:
const count =ref(0)
watch(count,(count,prevCount)=>{
})
监听多个数据源
监视reactive 类型的数据源:
const state = reactive({count:0,name:'zs'})
watch(
  [()=>state.count,()=>state.name], // Object.values(toRefs(state))
  ([count,name],[prevCount,prevName])=>{
    console.log(count)
    console.log(name)
    console.log(prevCount)
    console.log(prevName)
  },
  {
    lazy:true // 在watch 被创建的时候,不执行回调函数中的
  }
)
setTimeout(()=>{
  state.count++
  state.name='ls'
},1000)
监视ref 类型的数据源:
const count =ref(0)
const name=ref('zs')
watch(
  [count,name],
  ([count,name],[prevCount,prevName])=>{
    console.log(count,name)
    console.log(prevCount,prevName)
  },
  {
    lazy:true
  }
)
setTimeout(()=>{
  count.value++
  name.value='kobe'
},1000)
清除监听
在setup() 函数内创建的watch监听,会在当前组件被销毁的时候自动停止。如果想要明确地停止某个监听,可以调用watch()函数的返回值即可
const stop=watch(()=>{
})
stop()
在watch 中清除无效的异步任务
有时候,当被watch 监视的值发生变化时,或者watch本身被stop之后,我们期望能够清除那些无效的异步任务,
此时,watch回调函数中提供了一个cleanup registrator function 来执行清除的工作,这个清除函数会在如下情况下被调用:
watch被重复执行了
watch被强制stop了
<input type="text" v-model="keywords" />
const keywords = ref('')
// 异步任务:打印用户输入的关键词
const asyncPrint=val=>{
  return setTimeout(()=>{
    console.log(val)
  },1000)
}
// 定义 watch 监听
watch(
  keywords,
  (keywords,prevKeywords,onCleanup)=>{
    // 执行异步任务,并得到关闭异步任务的 timerId
    const timerId = asyncPrint(keywords)
    // 如果 watch 监听被重复执行了, 则会先清除上次未完成的异步任务
    oncleanup(()=>clearTimeout(timerId))
  },
  // watch 刚被创建的时候不执行
  {
    lazy:true
  }
)
// 把template 中需要的数据 return 出去
return {
  keywords
}

provide & inject 
provide() 和 inject() 可以实现嵌套组件之间的数据传递,这两个函数只能在setup()函数中使用,
父级组件中使用provide()函数向下传递数据,子组件中使用inject()获取上层传递过来的数据。

共享普通数据:
App.vue 根组件:
import { provide } from '@vue/composition-api'
setup(){
  provide('globalColor','red')
},
components:{
  LevelOne
}
LevelOne.vue 组件:
import { inject } from '@vue/composition-api'
setup(){
  const themeColor =inject('globalColor')
  return { themeColor }
},
components:{
  LevelTwo
}
LevelTwo.vue 组件:
import { inject } from '@vue/composition-api'
setup(){
  const themeColor =inject('globalColor')
  return { themeColor }
}

共享ref 响应式数据:
实现了点按钮切换主题颜色的功能,主要修改了 App.vue 组件中的代码,LevelOne.vue 和 LevelTwo.vue 中的代码不受任何改变:
<button @click="themeColor='red'">红色</button>
<button @click="themeColor='blue'">蓝色</button>
<button @click="themeColor='orange'">橘黄色</button>
App.vue 根组件:
import { provide } from '@vue/composition-api'
setup(){
  const themeColor = ref('red')
  provide('globalColor',themeColor)
},
components:{
  LevelOne
}

template refs:
通过 ref() 还可以引用页面上的元素或组件。
元素的引用:
<h3 ref="h3Ref">TemplateRefOne</h3>
import { ref, onMounted } from '@vue/composition-api'
  setup() {
    // 创建一个 DOM 引用
    const h3Ref = ref(null)
    // 在 DOM 首次加载完毕之后,才能获取到元素的引用
    onMounted(() => {
      // 为 dom 元素设置字体颜色
      // h3Ref.value 是原生DOM对象
      h3Ref.value.style.color = 'red'
    })
      // 把创建的引用 return 出去
    return {
      h3Ref
    }
  }
组件的引用:
TemplateRefOne.vue
<button @click="showNumber">获取TemplateRefTwo中的count值</button>
// 3. 为组件添加 ref 引用
<TemplateRefTwo ref="comRef" />
setup(){
  const comRef = ref(null)
  const showNumber=()=>{
    console.log(comRef.value.count)
  }
  return { comRef, showNumber }
},
components:{
  TemplateRefTwo
}
TemplateRefTwo.vue
<button @click="count+1">+1</button>
setup(){
  const count = ref(0)
  return { count }
}

使用模版语法遍历获取dom
<li v-for="(item,index) in 3" :key="index" :ref="(el)=>{divs[index]=el}">{{ item }}</li>
setup(){
  const divs = ref([])
  组件挂载后,即能通过divs.value获取遍历元素的所有dom
  return { divs }
}

createComponent:
这个函数不是必须的,除非你想要完美结合TypeScript 提供的类型推断来进行项目的开发。
这个函数仅仅提供了类型推断,方便在结合TypeScript 书写代码时,能为setup()中的props 提供完整的类型推断。
import {createComponent} from 'vue'
export default {
  props:{
    foo:String
  },
  setup(props){
    props.foo
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值