让我们一起愉快地学习vue3.0吧


Object.defineProperty => Proxy
重构了虚拟DOM
OptionApi => Composition API

setup是干啥的?
setup实际上是一个组件的入口,它运行在组件被实例化时候,props 属性被定义之后,实际上等价于 2 版本的beforeCreateCreated 这两个生命周期。
setup接受两个参数,第一个参数是props, 另一个参数是context

setup(props, ctx) {
  console.log(props, ctx)
}
let Child = {
  template: `<div>{{title}}</div>`,
  setup(props, context) {
    console.log(props)
  }
}

let App = {
  template: `
    <div class="container">
      <Child title="test props"/>
    </div>`,
  components: { Child }
}
Vue.createApp().mount(App, '#app')

reactive

const { reactive, toRefs } = Vue
let App = {
  template: `
    <div class="container">
        count: {{count}}
        <button @click="handlerCountAdd"> Click ++ </button>
    </div>`,
  setup() {
    const state = reactive({ count: 0 })
    const handlerCountAdd = () => {
      state.count++
    }
    return { ...toRefs(state), handlerCountAdd }
  }
}
Vue.createApp().mount(App, '#app')

toRefs

vue3提供的ref让我们有机会创建单个的响应式的对象,在setup函数中return出去之后,在模板中可直接访问

const App = {
  template: `
      <div class="container">
        {{value}}     
      </div>`,
  setup() {
    const value = ref(1)
    return { value }
  }
}
Vue.createApp().mount(App, '#app')
const App = {
  template: `
      <div class="container">
        {{state.value}}
      </div>`,
  setup() {
    const state = reactive({ value: 'reactive' })
    return { state }
  }
}
Vue.createApp().mount(App, '#app')
const App = {
  template: `
      <div class="container">
        {{value}}
      </div>`,
  setup() {
    const state = reactive({ value: 'reactive' })
    return toRefs(state)
  }
}
Vue.createApp().mount(App, '#app')

反转字符串 demo

let App = {
  template: `
    <div class="container">
        value: <input v-model="value"/>
        <br/>
        rvalue: {{rvalue}}
    </div>`,
  setup() {
    const state = reactive({
      value: '',
      rvalue: computed(() =>
        state.value
          .split('')
          .reverse()
          .join('')
      )
    })
    return toRefs(state)
  }
}
Vue.createApp().mount(App, '#app')

数据响应式

在Vue3中实现数据响应式的方案由Vue2中的Object.defineProperty 换成了 Proxy,关于数据响应式的Api上边说到了一些,还剩下effectwatch没有提及到,effect是数据响应式中重要的一部分,watchcomputed都是基于 effect 的.

let App = {
  template: `
    <div class="container">
        count: {{count}}
        <button @click="handlerCountAdd"> Click ++ </button>
    </div>`,
  setup() {
    const state = reactive({ count: 0, value: 1 })
    const handlerCountAdd = () => {
      state.count++
    }
    watch(
      () => state.count,
      val => {
        console.log('watch', state.count)
        console.log('watch', state.value)
      }
    )
    effect(() => {
      console.log('effect', state.count)
      console.log('effect', state.value)
    })
    return { ...toRefs(state), handlerCountAdd }
  }
}
Vue.createApp().mount(App, '#app')

effect 在响应式数据变化的时候就会执行,执行次数根据响应式数据的个数来决定

let App = {
  template: `
    <div class="container">
        <button @click="handlerCountAdd"> Click ++ </button>
    </div>`,
  setup() {
    const r = ref(1)
    const s = ref(1)
    const t = ref(1)
    const handlerCountAdd = () => {
      r.value *= 1
      s.value *= 2
      t.value *= 3
    }
    effect(() => {
      console.log('effect', [r.value, s.value, t.value])
    })
    return { handlerCountAdd }
  }
}
Vue.createApp().mount(App, '#app')

watch则点击一次 ,只会触发执行一次

let App = {
  template: `
    <div class="container">
        <button @click="handlerCountAdd"> Click ++ </button>
    </div>`,
  setup() {
    const state = reactive({ count: 0, value: 1 })
    const r = ref(1)
    const s = ref(1)
    const t = ref(1)
    const handlerCountAdd = () => {
      r.value *= 1
      s.value *= 2
      t.value *= 3
    }
    watch([r, s, t], val => {
      console.log('watch', val)
    })
    return { handlerCountAdd }
  }
}
Vue.createApp().mount(App, '#app')

生命周期

beforeCreate => setup(替代)

created => setup(替代)

beforeMount => onBeforeMount

mounted => onMounted

beforeUpdate => onBeforeUpdate

updated => onUpdated

beforeDestroy => onBeforeUnmount

destroyed => onUnmounted

errorCaptured => onErrorCaptured

全局配置

Vue2.x创建实例并且挂载DOM

import Vue from "vue";
import App from './App.vue'

new Vue({
  render: (h) => h(App)
}).$mount("#app");

Vue3新增api===>createApp 创建实例

createApp 会产生一个 app 实例,该实例拥有全局的可配置上下文

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

component

Vue2.x【注册或获取全局组件。注册还会自动使用给定的 id 设置组件的名称】

// 注册组件,传入一个选项对象 (自动调用 Vue.extend) 
Vue.component('my-component', { /* ... */ }) 

// 获取注册的组件 (始终返回构造器) 
var MyComponent = Vue.component('my-component')

Vue3【注册或获取全局组件注册还会自动使用给定的name组件 设置组件的名称】全局组件
基本vue2写法一致

import { createApp } from 'vue'

const app = createApp({})

// 注册组件,传入一个选项对象
app.component('my-component', {
  /* ... */
})

// 获取注册的组件 (始终返回构造器) 
const MyComponent = app.component('my-component', {})

globalProperties 【新增属性】

app.config.globalProperties.foo = 'bar'

app.component('child-component', {
  mounted() {
    console.log(this.foo) // 'bar'
  }
})

添加可在程序内的任何组件实例中访问的全局属性。当存在键冲突时,组件属性将优先
替代掉Vue2.xVue.prototype属性放到原型上的写法

// Vue2.x
Vue.prototype.$http = () => {}

// Vue3
const app = Vue.createApp({})
app.config.globalProperties.$http = () => {}

isCustomElement 【新增属性】

替代掉Vue2.xignoredElements

- Vue.config.ignoredElements = [
  // 用一个 `RegExp` 忽略所有“ion-”开头的元素
  // 仅在 2.5+ 支持
  /^ion-/
]

// 一些组件以'ion-'开头将会被解析为自定义组件

+ app.config.isCustomElement = tag => tag.startsWith('ion-')

指定一个方法来识别在Vue之外定义的自定义组件(例如,使用Web Component API)。如果组件符合这个条件,它就不需要本地或全局注册,Vue也不会抛出关于Unknown custom element的警告

注意,这个函数中不需要匹配所有原生HTML和SVG标记—Vue解析器会自动执行此检查

optionMergeStrategies

const app = Vue.createApp({
  mounted() {
    console.log(this.$options.hello)
  }
})

app.config.optionMergeStrategies.hello = (parent, child, vm) => {
  return `Hello, ${child}`
}

app.mixin({
  hello: 'Vue'
})

// 'Hello, Vue

定义自定义选项的合并策略。
合并策略接收在父实例options∗∗子实例∗∗options子实例options和∗∗子实例∗∗options,分别作为第一个和第二个参数。上下文Vue实例作为第三个参数传递

【自定义选项合并策略】mixin

const app = Vue.createApp({
  custom: 'hello!'
})

app.config.optionMergeStrategies.custom = (toVal, fromVal) => {
  console.log(fromVal, toVal)
  // => "goodbye!", undefined
  // => "hello!", "goodbye!"
  return fromVal || toVal
}

app.mixin({
  custom: 'goodbye!',
  created() {
    console.log(this.$options.custom) // => "hello!"
  }
})

optionMergeStrategies先获取到子实例的$options的mixin而没有父实例
【custom第一次改变从undefinedgoodbye--->打印"goodbye!", undefined

父实例的options替换掉子实例的options替换掉子实例的options替换掉子实例的options

【custom第二次从goodbye到hello!--->打印了"hello", "goodbye!"】

最后在打印`app.config.optionMergeStrategies.custom`返回的父实例的`$options`
无论如何this.options.custom最后会返回合并策略的return的值【使用场景利用父子组件的options.custom最后会返回合并策略的return的值【使用场景利用父子组件的options.custom最后会返回合并策略的return的值【使用场景利用父子组件的options,然后返回计算等操作得到所需要的值】optionMergeStrategies合并$options变化

directive

import { createApp } from 'vue'
const app = createApp({})

// 注册
app.directive('my-directive', {
  // 指令的生命周期
  // 在绑定元素的父组件被挂载之前调用
  beforeMount(el, binding, vnode) {},
  // 在挂载绑定元素的父组件时调用
  mounted(el, binding, vnode) {},
  // 在更新包含组件的VNode之前调用
  beforeUpdate(el, binding, vnode, prevNode) {},
  // 组件的VNode及其子组件的VNode更新之后调用
  updated(el, binding, vnode, prevNode) {},
  // 在卸载绑定元素的父组件之前调用
  beforeUnmount(el, binding, vnode) {},
  // 在卸载绑定元素的父组件时调用
  unmounted(el, binding, vnode) {}
})

// 注册 (指令函数)
app.directive('my-directive', (el, binding, vnode, prevNode) => {
  // 这里将会被 `mounted` 和 `updated` 调用
})

// getter,返回已注册的指令
const myDirective = app.directive('my-directive')
el
指令绑定到的元素。这可以用来直接操作DOM。

binding【包含下列属性的对象】

instance:使用指令的组件的实例

value:指令的绑定值,例如:v-my-directive="1 + 1"中,绑定值为 2

oldValue:指令绑定的前一个值,仅在 beforeUpdate 和 updated 钩子中可用。无论值是否改变都可用

arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"

modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }

dir:一个对象,在注册指令时作为参数传递;  举个例子,看下面指令
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})
dir就是下面的对象
{
  mounted(el) {
    el.focus()
  }
}
vnode
编译生成的虚拟节点

prevNode
前一个虚拟节点,仅在beforeUpdate和updated钩子中可用

tips:除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行

mount【类似Vue2.x】

在所提供的DOM元素上挂载应用程序实例的根组件

import { createApp } from 'vue'

const app = createApp({})
// 做一些准备
app.mount('#my-app')

provide/inject【Vue2.x一致】

该选项与inject一起使用,允许一个祖先组件作为其所有后代的依赖注入器,无论组件层次结构有多深,只要它们位于同一父链中就可以


provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。


如果在组件中两者都只能在当前活动组件实例的 setup() 中调用,详细请看依赖注入部分
import { createApp } from 'vue'

const app = createApp({
  provide: {
    user: 'John Doe'
  }
})
app.component('user-card', {
  inject: ['user'],
  template: `
    <div>
      {{ user }}
    </div>
  `
})

unmount【新增属性】

在所提供的DOM元素上卸载应用程序实例的根组件
import { createApp } from 'vue'

const app = createApp({})
// 做一些必要的准备
app.mount('#my-app')

// 应用程序将在挂载后5秒被卸载
setTimeout(() => app.unmount('#my-app'), 5000)

use【Vue2.x一致】

安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。
当 install 方法被同一个插件多次调用,插件将只会被安装一次。
setup
setup 函数是一个新的组件选项。作为在组件内使用 Composition API 的入口点

注意 setup 返回的 ref 在模板中会自动解开,不需要写 .value【setup 内部需要.value】

调用时机

创建组件实例,然后初始化 props ,紧接着就调用setup 函数。从生命周期钩子的视角来看,它会在 beforeCreate 钩子之前被调用

如果 setup 返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文

参数
props 作为其第一个参数

注意 props 对象是响应式的,watchEffect 或 watch 会观察和响应 props 的更新
不要解构 props 对象,那样会使其失去响应性
```js

```js
export default {
  props: {
    name: String,
  },
  setup(props) {
    console.log(props.name)
     watchEffect(() => {
      console.log(`name is: ` + props.name)
    })
  },
}
第二个参数提供了一个上下文对象【从原来 2.x 中 this 选择性地暴露了一些 property(attrs/emit/slots)】

attrs 和 slots 都是内部组件实例上对应项的代理,可以确保在更新后仍然是最新值。所以可以解构,无需担心后面访问到过期的值

为什么props作为第一个参数?

组件使用 props 的场景更多,有时候甚至只使用 props
将 props 独立出来作为第一个参数,可以让 TypeScript 对 props 单独做类型推导,不会和上下文中的其他属性相混淆。这也使得 setup 、 render 和其他使用了 TSX 的函数式组件的签名保持一致

this 在 setup() 中不可用。由于 setup() 在解析 2.x 选项前被调用,setup() 中的 this 将与 2.x 选项中的 this 完全不同。同时在 setup() 和 2.x 选项中使用 this 时将造成混乱

setup(props, { attrs }) {
    // 一个可能之后回调用的签名
    function onClick() {
      console.log(attrs.foo) // 一定是最新的引用,没有丢失响应性
    }
  }

响应式系统 API

reactive
desc: 接收一个普通对象然后返回该普通对象的响应式代理【等同于 2.x 的 Vue.observable()】

tips:Proxy对象是目标对象的一个代理器,任何对目标对象的操作(实例化,添加/删除/修改属性等等),都必须通过该代理器。因此我们可以把来自外界的所有操作进行拦截和过滤或者修改等操作

响应式转换是“深层的”:会影响对象内部所有嵌套的属性。基于 ES2015 的 Proxy 实现,返回的代理对象不等于原始对象。建议仅使用代理对象而避免依赖原始对象

reactive 类的 api 主要提供了将复杂类型的数据处理成响应式数据的能力,其实这个复杂类型是要在object array map set weakmap weakset 这五种之中【如下源码,他会判断是否是五类以及是否被冻结】
因为是组合函数【对象】,所以必须始终保持对这个所返回对象的引用以保持响应性【不能解构该对象或者展开】例如 const { x, y } = useMousePosition()或者return { ...useMousePosition() }
function useMousePosition() {
    const pos = reactive({
        x: 0,
        y: 0,
      })
    return pos
}
toRefs API 用来提供解决此约束的办法——它将响应式对象的每个 property 都转成了相应的 ref【把对象转成了ref】。
 function useMousePosition() {
    const pos = reactive({
        x: 0,
        y: 0,
      })
    return toRefs(pos)
}
// x & y 现在是 ref 形式了!
const { x, y } = useMousePosition()

ref

接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value
const count = ref(0)
console.log(count.value) // 0
如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换
陷阱
setup 中return返回会自动解套【在模板中不需要.value】
ref 作为 reactive 对象的 property 被访问或修改时,也将自动解套 .value 
const count = ref(0)
/*当做reactive的对象属性----解套*/
const state = reactive({
  count,
})
/* 不需要.value*/
console.log(state.count) // 0

/*修改reactive的值*/
state.count = 1
/*修改了ref的值*/
console.log(count.value) // 1
注意如果将一个新的 ref 分配给现有的 ref, 将替换旧的 ref
/*创建一个新的ref*/
const otherCount = ref(2)

/*赋值给reactive的旧的ref,旧的会被替换掉*/
state.count = otherCount
/*修改reactive会修改otherCount*/
console.log(state.count) // 2
/*修改reactive会count没有被修改 */
console.log(count.value) // 1
嵌套在 reactive Object 中时,ref 才会解套。从 Array 或者 Map 等原生集合类中访问 ref 时,不会自动解套【自由数据类型是Object才会解套,array  map  set   weakmap  weakset集合类 访问 ref 时,不会自动解套】
const arr = reactive([ref(0)])
// 这里需要 .value
console.log(arr[0].value)

const map = reactive(new Map([['foo', ref(0)]]))
// 这里需要 .value
console.log(map.get('foo').value)
心智负担上 ref   vs  reactive

在普通 JavaScript 中区别声明基础类型变量与对象变量时一样区别使用 ref 和 reactive
所有的地方都用 reactive,然后记得在组合函数返回响应式对象时使用 toRefs。这降低了一些关于 ref 的心智负担

readonly
传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的【返回一个永远不会变的只读代理】【场景可以参数比对等】
const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 依赖追踪
  console.log(copy.count)
})

// original 上的修改会触发 copy 上的侦听
original.count++

// 无法修改 copy 并会被警告
copy.count++ // warning!
reactive响应式系统工具集
isProxy

 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

isReactive

 检查一个对象是否是由 reactive 创建的响应式代理
import { reactive, isReactive } from 'vue'
const state = reactive({
      name: 'John'
    })
console.log(isReactive(state)) // -> true
如果这个代理是由 readonly 创建的,但是又被 reactive 创建的另一个代理包裹了一层,那么同样也会返回 true
import { reactive, isReactive, readonly } from 'vue'
const state = reactive({
      name: 'John'
    })
// 用readonly创建一个只读响应式对象plain
const plain = readonly({
    name: 'Mary'
})
//readonly创建的,所以isReactive为false
console.log(isReactive(plain)) // -> false  

// reactive创建的响应式代理对象包裹一层readonly,isReactive也是true,isReadonly也是true
const stateCopy = readonly(state)
console.log(isReactive(stateCopy)) // -> true
isReadonly

检查一个对象是否是由 readonly 创建的只读代理

reactive高级响应式系统API
toRaw

返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发更改。不建议一直持有原始对象的引用【**不建议赋值给任何变量**】。请谨慎使用

被toRaw之后的对象是没有被代理/跟踪的的普通对象
const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true
console.log(toRaw(reactiveFoo) !== reactiveFoo) // true
markRaw
显式标记一个对象为“永远不会转为响应式代理”,函数返回这个对象本身。

【markRaw传入对象,返回的值是永远不会被转为响应式代理的】
const foo = markRaw({
    name: 'Mary'
})
console.log(isReactive(reactive(foo))) // false
被 markRaw 标记了,即使在响应式对象中作属性,也依然不是响应式的
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
markRaw注意点


markRaw和 shallowXXX 一族的 API允许选择性的覆盖reactive或者readonly 默认创建的 "深层的" 特性【响应式】/或者使用无代理的普通对象


设计这种「浅层读取」有很多原因

一些值的实际上的用法非常简单,并没有必要转为响应式【例如三方库的实例/省市区json/Vue组件对象】
当渲染一个元素数量庞大,但是数据是不可变的,跳过 Proxy 的转换可以带来性能提升



这些 API 被认为是高级的,是因为这种特性仅停留在根级别,所以如果你将一个嵌套的,没有 markRaw 的对象设置为 reactive 对象的属性,在重新访问时,你又会得到一个 Proxy 的版本,在使用中最终会导致标识混淆的严重问题:执行某个操作同时依赖于某个对象的原始版本和代理版本(标识混淆在一般使用当中应该是非常罕见的,但是要想完全避免这样的问题,必须要对整个响应式系统的工作原理有一个相当清晰的认知)。
const foo = markRaw({
  nested: {},
})

const bar = reactive({
  // 尽管 `foo` 己经被标记为 raw 了, 但 foo.nested 并没有
  nested: foo.nested,
})

console.log(foo.nested === bar.nested) // false
foo.nested没有被标记为(永远不会转为响应式代理),导致最后的值一个reactive




shallowReactive
只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理,而只是保留原样【第一层是响应式代理,深层次只保留原样(不具备响应式代理)】
const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2,
  },
})

// 变更 state 的自有属性是响应式的【第一层次响应式】
state.foo++
// ...但不会深层代理【深层次不是响应式】(渲染性能)
isReactive(state.nested) // false
state.nested.bar++ // 非响应式
shallowReadonly
类似于shallowReactive,区别是:

第一层将会是响应式代理【第一层修改属性会失败】,属性为响应式
深层次的对象属性可以修改,属性不是响应式

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2,
  },
})

// 变更 state 的自有属性会失败
state.foo++
// ...但是嵌套的对象是可以变更的
isReadonly(state.nested) // false
state.nested.bar++ // 嵌套属性依然可修改
ref 响应式系统工具集
unref
 unref是val = isRef(val) ? val.value : val 的语法糖
unref(ref(0))===unref(0)===0   返回number
function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x) // unwrapped 一定是 number 类型
}
toRef
toRef 可以用来为一个 reactive 对象的属性【某个属性区别toRefs每一个属性】创建一个 ref。这个 ref 可以被传递并且能够保持响应性
const state = reactive({
  foo: 1,
  bar: 2,
})

//reactive获取单个属性转为ref【fooRef只是一个代理】
const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) // 2

state.foo++
console.log(fooRef.value) // 3

toRefs

把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应
const state = reactive({
  foo: 1,
  bar: 2,
})

const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型如下:

{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// ref 对象 与 原属性的引用是 "链接" 上的
state.foo++
console.log(stateAsRefs.foo) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3
可以通过toRefs返回可解构的reactive,因为toRefs包裹之后返回一一对应的ref属性   
function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2,
  })

  // 对 state 的逻辑操作

  // 返回时将属性都转为 ref
  return toRefs(state)
}

export default {
  setup() {
    // 可以解构,不会丢失响应性
    const { foo, bar } = useFeatureX()

    return {
      foo,
      bar,
    }
  },
}

isRef

检查一个值是否为一个 ref 对象
ref 高级响应式系统API
customRef
用于自定义一个 ref,可以显式地控制依赖追踪和触发响应,接受一个工厂函数,两个参数分别是用于追踪的 track 与用于触发响应的 trigger,并返回一个一个带有 get 和 set 属性的对象【实际上就是手动 track追踪 和 trigger触发响应】

以下代码可以使得v-model防抖

function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
          /*初始化手动追踪依赖讲究什么时候去触发依赖收集*/
        track()
        return value
      },
      set(newValue) {
          /*修改数据的时候会把上一次的定时器清除【防抖】*/
        clearTimeout(timeout)
        timeout = setTimeout(() => {
            /*把新设置的数据给到ref数据源*/
          value = newValue
            /*再有依赖追踪的前提下触发响应式*/
          trigger()
        }, delay)
      },
    }
  })
}

setup() {
    return {
        /*暴露返回的数据加防抖*/
      text: useDebouncedRef('hello'),
    }
  }

shallowRef

创建一个 ref ,将会追踪它的 .value 更改操作,但是并不会对变更后的 .value 做响应式代理转换(即变更不会调用 reactive)

前面我们说过如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换,通过shallowRef创建的ref,将不会调用reactive【对象不会是响应式的】
const refOne = shallowRef({});
refOne.value = { id: 1 };
refOne.id == 20;
console.log(isReactive(refOne.value),refOne.value);//false  { id: 1 }
triggerRef 【与shallowRef配合】
手动执行与shallowRef相关的任何效果
const shallow = shallowRef({
  greet: 'Hello, world'
})

// 第一次运行打印 "Hello, world" 
watchEffect(() => {
  console.log(shallow.value.greet)
})

// 这不会触发效果,因为ref是shallow
shallow.value.greet = 'Hello, universe'

// 打印 "Hello, universe"
triggerRef(shallow)

Computed and watch【监控变化】

computed


传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象【默认传入的是get函数的对象】


传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态



const count = ref(1)
/*不支持修改【只读的】 */
const plusOne = computed(() => count.value + 1)
plusOne.value++ // 错误!

/*【可更改的】 */
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  },
})

plusOne.value = 1
console.log(count.value) // 0

watchEffect

立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数
computed与watchEffect区别:

computed计算属性可通过setup return,再模板中使用,watchEffect不能;
computed可以使用多个,并且对多个属性进行不同的响应计算,watchEffect会存在副作用

const count = ref(0)

watchEffect(() => console.log(count.value))
// -> 打印出 0

setTimeout(() => {
  count.value++
  // -> 打印出 1
}, 100)

停止观察


当在组件的setup()函数或生命周期钩子期间调用watchEffect时,监视程序会链接到组件的生命周期,并在卸载组件时自动停止
一般情况下watchEffect返回可以stop 操作,停止监听程序
const stop = watchEffect(() => {
  /* ... */
})

// 停止监听程序
stop()

副作用(函数式编程)

一个带有副作用的函数不仅只是简单的返回一个值,还干了一些其他的事情,比如:

修改一个变量
直接修改数据结构
设置一个对象的成员
抛出一个异常或以一个错误终止
打印到终端或读取用户的输入
读取或写入一个文件
在屏幕上绘画


buyCoffee的例子(p3):函数只不过是需要返回一杯咖啡,可是却对费用进行了持久化操作(产生副作用),我们可以在buyCoffee方法返回咖啡时也把费用作为值一并返回,将费用这条记录交给其他程序来做持久化,以此来去除副作用 ====》通过把这些副作用推到程序的外层,来转换任何带有副作用的函数(纯的内核和一层很薄的外围来处理副作用)


如果一个函数内外有依赖于外部变量或者环境时,常常我们称之为其有副作用,如果我们仅通过函数签名不打开内部代码检查并不能知道该函数在干什么,作为一个独立函数我们期望有明确的输入和输出,副作用是bug的发源地,作为程序员开发者应尽量少的开发有副作用的函数或方法,副作用也使得方法通用性下降不适合扩展和可重用性

清除副作用
[^]: watchEffect函数都是副作用
在一些时候监听函数将执行异步副作用【一个响应式依赖被修改了,会做其他事情】,这些响应需要在其失效时清除(例如在效果完成前状态改变)。effect函数接收一个onInvalidate 函数作入参, 用来注册清理失效时的回调。这个 invalidation函数 在什么时候会被调用:


监听函数重新被执行的时候【响应式依赖的数据被修改】


监听停止的时候(如果watchEffect在setup()或者生命周期函数中被使用的时候组件会被卸载)【停止观察】
watchEffect(onInvalidate => {
  /*这是个异步操作*/
  const token = performAsyncOperation(id.value)//id依赖
  onInvalidate(() => {
    // id被修改了或者监听停止了会触发token.cancel()事件【这块区域的代码】.
    // 这里是异步事件的话,前面的peding的异步操作无效【这里的异步事件只执行一次】
     token.cancel()/*异步操作*/
    console.log('onInvalidate')
  })
})

从上面看:我们之所以是通过传入一个函数去注册失效回调,而不是从回调返回它(如 React useEffect 中的方式),是因为返回值对于异步错误处理很重要

  const data = ref(null)
  watchEffect(async onInvalidate => {
    onInvalidate(() => {...}) // 我们在Promise的resolves之前注册清理函数(cleanup function)
    data.value = await fetchData(props.id)
  })

  我们知道异步函数都会隐式地返回一个 Promise,但是清理副作用的函数必须要在 Promise 被 resolve 之前被注册。另外,Vue 依赖这个返回的 Promise 来自动处理 Promise 链上的潜在错误
副作用刷新时机
Vue 的响应式系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个 tick 中多个状态改变导致的不必要的重复调用。在核心的具体实现中, 组件的更新函数也是一个被侦听的副作用。当一个用户定义的副作用函数进入队列时, 会在所有的组件更新后执行
<template>
  <div>{{ count }}</div>
</template>

<script>
  export default {
    setup() {
      const count = ref(0)

      watchEffect(() => {
        console.log(count.value)
      })

      return {
        count,
      }
    },
  }
</script>
count 会在初始运行时同步打印出来


更改 count 时,将在组件更新后执行副作用

初始化运行是在组件 mounted 之前执行的【你希望在编写副作用函数时访问 DOM(或模板 ref),请在 onMounted 钩子中进行】
onMounted(() => {
  watchEffect(() => {
    // 在这里可以访问到 DOM 或者 template refs
  })
})

如果副作用需要同步或在组件更新之前重新运行,我们可以传递一个拥有 flush 属性的对象作为选项(默认为 'post')
// 同步运行
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'sync',
  }
)

// 组件更新前执行
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'pre',
  }
)

侦听器调试【响应式调试用的】

onTrack 和 onTrigger 选项可用于调试一个侦听器的行为。

当一个 reactive 对象属性或一个 ref 作为依赖被追踪时,将调用 onTrack【调用次数为被追踪的数量】
依赖项变更会导致重新追踪依赖,从而onTrack被调用【调用次数为被追踪的数量】
依赖项变更导致副作用被触发时,将调用 onTrigger

这两个回调都将接收到一个包含有关所依赖项信息的调试器事件。建议在以下回调中编写 debugger 语句来检查依赖关系:【onTrack 和 onTrigger 仅在开发模式下生效】
watchEffect(
  () => {
    /* 副作用的内容 */
  },
  {
    onTrigger(e) {
      /*副作用依赖修改*/
      debugger
    },
    onTrack(e) {
      /*副作用依赖修改*/
      debugger
    },
  }
)

watch

watch API 完全等效于 2.x watch 中相应的选项。watch 需要侦听特定的数据源,并在回调函数中执行副作用【默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调】
watch允许我们:

懒执行副作用
更明确哪些状态的改变会触发侦听器重新运行副作用
访问侦听状态变化前后的值

侦听单个数据源
侦听器的数据源可以是一个拥有返回值的 getter 函数,也可以是 ref:
// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

侦听多个数据源


watcher 也可以使用数组来同时侦听多个源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

与 watchEffect 共享的行为

watch 和 watchEffect 在停止侦听, 清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入),副作用刷新时机 和 侦听器调试 等方面行为一致
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar],onInvalidate) => {
  /* ... */
  onInvalidate(() => {...})
},
  {
    onTrigger(e) {
      /*副作用依赖修改*/
      debugger
    },
    onTrack(e) {
      /*副作用依赖修改*/
      debugger
    },
  })

生命周期钩子函数

与 2.x 版本生命周期相对应的组合式 API

beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured

import { onMounted, onUpdated, onUnmounted } from 'vue'
setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }
这些生命周期钩子注册函数只能在 setup() 期间同步使用, 因为它们依赖于内部的全局状态来定位当前组件实例(正在调用 setup() 的组件实例), 不在当前组件下调用这些函数会抛出一个错误。
组件实例上下文也是在生命周期钩子同步执行期间设置的,因此,在卸载组件时,在生命周期钩子内部同步创建的侦听器和计算状态也将自动删除。
新增的钩子函数
除了和 2.x 生命周期等效项之外,组合式 API 还提供了以下调试钩子函数:

onRenderTracked
onRenderTriggered

两个钩子函数都接收一个 DebuggerEvent,与 watchEffect 参数选项中的 onTrack 和 onTrigger 类似:
export default {
  onRenderTracked(e){
      debugger
    // 检查有响应和追踪的依赖性
 },
  onRenderTriggered(e) {
    debugger
    // 检查哪个依赖性导致组件重新渲染
  },
}

Vue提供的内置组件

component 与Vue2.x一致
渲染一个“元组件”为动态组件。依 is 的值,来决定哪个组件被渲染。
<!-- 动态组件由 vm 实例的 `componentId` property 控制 -->
<component :is="componentId"></component>

<!-- 也能够渲染注册过的组件或 prop 传入的组件 -->
<component :is="$options.components.child"></component>

transition 与 Vue2.x 【基本】 一致有差异

Props新增:
persisted - boolean 如果为true,则表示这是一个转换,实际上不会插入/删除元素,而是切换显示/隐藏状态。 transition 过渡挂钩被注入,但会被渲染器跳过。 相反,自定义指令可以通过调用注入的钩子(例如v-show)来控制过渡
enter-class----->enter-from-class
leave-class----->leave-from-class

事件
before-appear


transition-group 与 Vue2.x 一致
slot 与 Vue2.x 一致
teleport 【新增组件】


Props


to - string 必填属性,必须是一个有效的query选择器,或者是元素(如果在浏览器环境中使用)。中的内容将会被放置到指定的目标元素中
<!-- 正确的 -->
<teleport to="#some-id" />
<teleport to=".some-class" />
 /*元素*/
<teleport to="[data-teleport]" />

<!-- 错误的 -->
<teleport to="h1" />
<teleport to="some-string" />
disabled  - boolean 这是一个可选项 ,做一个是可以用来禁用的功能,这意味着它的插槽内容不会移动到任何地方,而是按没有teleport组件一般来呈现【默认为false】
<teleport to="#popup" :disabled="displayVideoInline">
  <h1>999999</h1>
</teleport>

注意,这将移动实际的DOM节点,而不是销毁和重新创建,并且还将保持任何组件实例是活动的。所有有状态HTML元素(比如一个正在播放的视频)将保持它们的状态。【控制displayVideoInline并不是销毁重建,它保持实例是存在的,不会被注销】


关于Teleport 其他内容
Vue鼓励我们通过将UI和相关行为封装到组件中来构建UI。我们可以将它们彼此嵌套在一起,以构建构成应用程序UI的树
但是,有时组件模板的一部分逻辑上属于这个组件,而从技术角度来看,最好将这一部分模板移到DOM中的其他地方,放到Vue应用程序之外
一个常见的场景是创建一个包含全屏模态的组件。在大多数情况下,您希望模态的逻辑驻留在组件中,但是模态框的定位问题很快就很难通过CSS解决,或者需要更改组件的组成
考虑下面的HTML结构:
<body>
  <div style="position: relative;">
    <h3>Tooltips with Vue 3 Teleport</h3>
    <div>
      <modal-button></modal-button>
    </div>
  </div>
</body>
让我们看看 mode -button
该组件将有一个button元素来触发模态的打开,还有一个div元素,其类为.modal,它将包含模态的内容和一个自关闭按钮
const app = Vue.createApp({});

app.component('modal-button', {
  template: `
    <button @click="modalOpen = true">
        Open full screen modal!
    </button>

    <div v-if="modalOpen" class="modal">
      <div>
        I'm a modal! 
        <button @click="modalOpen = false">
          Close
        </button>
      </div>
    </div>
  `,
  data() {
    return { 
      modalOpen: false
    }
  }
})
当在初始HTML结构中使用这个组件时,我们可以看到一个问题——模态被呈现在深嵌套的div中,模态的绝对位置以父div相对位置作为参考。


Teleport提供了一种干净的方式,允许我们控制DOM中希望在哪个父节点下呈现HTML片段,而不必诉诸全局状态或将其拆分为两个组件。
让我们修改我们的modal-button来使用并告诉Vue "teleport this HTML to the "body"标签"。
app.component('modal-button', {
  template: `
    <button @click="modalOpen = true">
        Open full screen modal! (With teleport!)
    </button>

    <teleport to="body">
      <div v-if="modalOpen" class="modal">
        <div>
          I'm a teleported modal! 
          (My parent is "body")
          <button @click="modalOpen = false">
            Close
          </button>
        </div>
      </div>
    </teleport>
  `,
  data() {
    return { 
      modalOpen: false
    }
  }
})
与Vue组件一起使用
如果包含一个Vue组件,它将仍然是的父组件的逻辑子组件
const app = Vue.createApp({
  template: `
    <h1>Root instance</h1>
    <parent-component />
  `
})

app.component('parent-component', {
  template: `
    <h2>This is a parent component</h2>
    <teleport to="#endofbody">
      <child-component name="John" />
    </teleport>
  `
})

app.component('child-component', {
  props: ['name'],
  template: `
    <div>Hello, {{ name }}</div>
  `
})
在这种情况下,即使在不同的地方呈现child-component,它仍将是parent-componen的子组件【而不是爷爷组件】,并将从其父组件接收一个name 的props
这也意味着来自父组件的注入如预期的那样工作,并且子组件将嵌套在Vue Devtools的父组件之下,而不是放在实际内容移动到的地方
对同一目标使用多次teleports
一个常见的用例场景是一个可重用的组件,该组件可能同时有多个活动实例。对于这种场景,多个组件可以将它们的内容挂载到相同的目标元素。这个顺序将是一个简单的附加—稍后的挂载将位于目标元素中较早的挂载之后。
<teleport to="#modals">
  <div>A</div>
</teleport>
<teleport to="#modals">
  <div>B</div>
</teleport>

<!-- result-->
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>
依赖注入Provide / Inject
provide 和 inject 提供依赖注入,功能类似 2.x 的 provide/inject。两者都只能在当前活动组件实例的 setup() 中调用
例如,如果我们想在根组件上提供一个book name,并将其inject到子组件上
import { provide, inject } from 'vue'

const RootComponent = {
  setup() {
    provide('book', 'Vue 3 guide')
  }
}

const MyBook = {
  setup() {
    const book = inject(
      'book',
      'Eloquent Javascript' /* 选项的默认值,假如父组件不提供值就返回默认 */
    )
    return {
      book
    }
  }
}
inject 接受一个可选的的默认值作为第二个参数。如果未提供默认值,并且在 provide 上下文中未找到该属性,则 inject 返回 undefined。
如果我们需要提供或注入多个值,我们可以通过随后分别调用provide或inject来实现【多次调用】
import { provide, inject } from 'vue'

const RootComponent = {
  setup() {
    provide('book', 'Vue 3 guide')
    provide('year', '2020')
  }
}

const MyBook = {
  setup() {
    const book = inject(
      'book',
      'Eloquent Javascript' /* 选项的默认值,假如父组件不提供值就返回默认 */
    )
    const year = inject('year')
    return {
      book,
      year
    }
  }
}
注入的响应性
可以使用 ref 或 reactive 来保证 provided 和 injected 之间值的响应
import { ref, reactive } from 'vue'

// 提供者
setup() {
  const book = reactive({
    title: 'Vue 3 Guide',
    author: 'Vue Team'
  })
  const year = ref('2020')

 /*提供reactive响应式*/
  provide('book', book)
 /*提供ref响应式*/
  provide('year', year)
}

// 消费者
setup() {
  const book = inject('book')
  const year = inject('year')
 /*响应式*/
  return { book, year }
}
现在,当提供者组件上的book或year发生变化时,我们可以观察到它们在注入的组件上的变化。

警告:我们不建议改变一个被注入的反应性属性【子组件去修改数据流】,因为它会破坏Vue的单向数据流。相反,尝试在提供值【父组件去修改】的地方改变值,或者提供一个方法来改变值
import { ref, reactive } from 'vue'

// in provider
setup() {
  const book = reactive({
    title: 'Vue 3 Guide',
    author: 'Vue Team'
  })

  function changeBookName() {
    book.title = 'Vue 3 Advanced Guide'
  }

  provide('book', book)
  provide('changeBookName', changeBookName)
}

// in consumer
setup() {
  const book = inject('book')
  const changeBookName = inject('changeBookName')

  return { book, changeBookName }
}

指令
v-text 【Vue2.x一致】
v-html【Vue2.x一致】
v-show【Vue2.x一致】
v-if【Vue2.x一致】
v-else【Vue2.x一致】
v-else-if【Vue2.x一致】
v-for【Vue2.x一致】
v-on【Vue2.x一致】
v-bind 【Vue2.x 修饰符差异】
修饰符

.prop 去除
.sync 去除
.camel 将 kebab-case attribute 名转换为 camelCase

v-model【Vue2.x一致】
v-slot【Vue2.x一致】
v-cloak【Vue2.x一致】
v-once 【Vue2.x一致】
v-pre【Vue2.x一致】
v-is【新增】

注意:本节只影响在页面的HTML中直接编写Vue模板的情况



限制:原生html元素


使用:
 使用in-DOM模板时,该模板应遵守本机HTML解析规则。 某些HTML元素(例如,,和)对可以在其中显示哪些元素有限制,而某些元素(例如,和)只能 出现在某些其他元素内。 解决方法是,我们可以在这些元素上使用v-is指令【作用就是转成组件的名字】



警告v-is 功能 像一个动态2.x :is 绑定 所以要根据注册的名称渲染组件,它的值应该是一个JavaScript字符串
<!-- 不正确的, 不会出现任何渲染 -->
<tr v-is="blog-post-row"></tr>

<!-- 正确 -->
<tr v-is="'blog-post-row'"></tr>
全局API
createApp
返回一个应用程序实例,提供了一个应用程序上下文。应用程序实例挂载的整个组件树共享相同的上下文
const app = Vue.createApp({})

参数

该函数接收一个根组件选项对象作为第一个参数

const app = Vue.createApp({
  data() {
    return {
      ...
    }
  },
  methods: {...},
  computed: {...}
  setup(){...}
  ...
})
使用第二个参数,我们可以将根组件props 传递给应用

<div id="app">
  <!-- 这里将会显示 'Evan' -->
  {{ username }}
</div>

const app = Vue.createApp(
  {
    props: ['username']
  },
  { username: 'Evan' }
)
h
返回“虚拟节点”,通常缩写为VNode:一个简单的对象,它包含描述Vue应该在页面上渲染何种类型的节点的信息,包括对任何子节点的描述。你可以手动阅读render functions
render() {
  return Vue.h('h1', {}, 'Some title')
}

参数
接受三个参数tag, props and children

tag: 

类型:String | Object | Function | null
详情:一个HTML标签名,一个组件,一个异步组件或null。使用null将渲染成注释。此参数是必需的

props
类型:Object
详情:模板中使用的attributes、props 和events 对应的对象。可选

children
类型: String | Array | Object

详情:
Children VNodes,使用h()构建,或使用字符串来获取“text VNodes”或带有槽的对象。可选

const aaa = {
  props: {
    someProp: String
  },
  setup(props) {
    console.log(props, "dsadasdasddasds");
  },
  render() {
    return h(
      "h2",
        // {Object}props
        //与props,attributes和events相对应的对象
        //我们将在template中使用。
        // 可选的。
        {style: {"font-size": "20px",
          color: "#136"}},
          [this.someProp,this.$slots.default()]);
        }
};
app.component("anchored-heading", {
  render() {
    return h(
        /*
        // {String | Object | Function | null}标签
        // HTML标记名称,组件,异步组件或null。
        //使用null将渲染注释。
        //必填
        */
      "h" + this.level, // tag name
        // {Object}props
        //与props,attributes和events相对应的对象
        //我们将在template中使用。
        // 可选的。
      {}, 
        // {String | Array | Object} children
        //使用`h()`构建的子级VNode,
        //或使用字符串获取“文本VNodes”或
        //具有插槽的对象。
        // 可选的。
      [
        "Some text comes first.",
        h("h1", "A headline"),
        h(aaa, {
          someProp: "foobar"
        })
      ]  );},
});
 Vue.h(
        'a',
        {
          name: headingId,
          href: '#' + headingId
        },
        this.$slots.default()
      )
    ])

限制
VNodes 必须独一无二

组件树中的所有vnode必须是唯一的。这意味着下面的渲染函数是无效的
 render() {
     const myParagraphVNode = Vue.h('p', 'hi')
      return Vue.h('div', [
        // 表示惊讶 - 副本复制 VNodes!
        myParagraphVNode, myParagraphVNode
      ])
  }

如果您确实想多次复制相同的元素/组件,则可以使用工厂函数进行复制。 例如,以下呈现函数是呈现20个相同段落的完美有效方法:
render() {
  return Vue.h('div',
    Array.apply(null, { length: 20 }).map(() => {
      return Vue.h('p', 'hi')
    })
  )
}
用普通的JavaScript替换模板特性
v-if and v-for
在任何地方都可以用普通JavaScript轻松完成,Vue渲染functions 都不提供专有的替代方案。例如,在使用v-if和v-for的模板中
<ul v-if="items.length">
  <li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>
==>
props: ['items'],
render() {
  if (this.items.length) {
    return Vue.h('ul', this.items.map((item) => {
      return Vue.h('li', item.name)
    }))
  } else {
    return Vue.h('p', 'No items found.')
  }
}   
v-model
v-model指令被扩展到modelValue和onUpdate:modelValue道具在模板编译期间,我们将不得不自己提供这些props 
props: ['modelValue'],
render() {
  return Vue.h(SomeComponent, {
    modelValue: this.modelValue,
    'onUpdate:modelValue': value => this.$emit('update:modelValue', value)
  })
}
v-on
我们必须为事件处理程序提供一个适当的prop名称,例如,为了处理click事件,prop名称应该是onClick
render() {
  return Vue.h('div', {
    onClick: $event => console.log('clicked', $event.target)
  })
}
事件修饰符
对于.passive、.capture和.once事件修饰符,Vue提供了处理程序的对象语法
render() {
  return Vue.h('input', {
    onClick: {
      handler: this.doThisInCapturingMode,
      capture: true
    },
    onKeyUp: {
      handler: this.doThisOnce,
      once: true
    },
    onMouseOver: {
      handler: this.doThisOnceInCapturingMode,  //事件
      once: true, //是否触发一次
      capture: true 
    },
  })
}
对于所有其他事件和键修饰符,不需要特殊的API,因为我们可以在处理程序中使用事件方法

render() {
  return Vue.h('input', {
    onKeyUp: event => {
      // 如果发出事件的元素不存在,则中止事件绑定到的元素
      if (event.target !== event.currentTarget) return
      // 同时如果按下的键不是enter键key (13)以及shift键没有按下
      if (!event.shiftKey || event.keyCode !== 13) return
      // 停止事件传播
      event.stopPropagation()
      // 阻止此元素的默认keyup处理程序
      event.preventDefault()
      // ...
    }
  })
}
Slots
你可以访问插槽内容this.$slots在VNodes数组的
render() {
  // `<div><slot></slot></div>`
  return Vue.h('div', {}, this.$slots.default())
}
props: ['message'],
render() {
  // `<div><slot :text="message"></slot></div>`
  return Vue.h('div', {}, this.$slots.default({
    text: this.message
  }))
}
使用render函数将槽传递给子组件
render() {
  // `<div><child v-slot="props"><span>{{ props.text }}</span></child></div>`
  return Vue.h('div', [
    Vue.h('child', {}, {
      // 通过`slots'作为子对象
      // in the form of { name: props => VNode | Array<VNode> }
      default: (props) => Vue.h('span', props.text)
    })
  ])
}
JSX
如果我们要编写大量的渲染函数,编写这样的东西可能会让人感到痛苦
Vue.h(
  'anchored-heading',
  {
    level: 1
  },
  [Vue.h('span', 'Hello'), ' world!']
)
特别是当模板版本相比之下如此简洁的时候
<anchored-heading :level="1"> <span>Hello</span> world! </anchored-heading>
这就是为什么有一个Babel插件可以在Vue中使用JSX,让我们回到更接近模板的语法
import AnchoredHeading from './AnchoredHeading.vue'

new Vue({
  el: '#demo',
  render() {
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  }
})
defineComponent【组件】
在实现方面,defineComponent只会执行返回传递给它的对象的操作。 但是,就类型而言,返回的值具有人工渲染功能,TSX和IDE工具支持的构造函数的综合类型
参数
具有组件选项的对象
import { defineComponent } from 'vue'

const MyComponent = defineComponent({
  data() {
    return { count: 1 }
  },
  methods: {
    increment() {
      this.count++
    }
  }
})
defineAsyncComponent 【异步组件】
创建只在必要时加载的异步组件
参数
对于基本用法,defineAsyncComponent可以接受返回Promise的工厂函数。当您从serve检索到组件定义时,应该调用Promise的解析回调。您还可以调用reject(reason)来指示加载失败。
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
   /*或者*/
  import('./components/AsyncComponent.vue')
   /*或者*/
  new Promise((resolve, reject) => {
  /*可以reject*/
      resolve({
        template: '<div>I am async!</div>'
      })
    })
)

app.component('async-component', AsyncComp)
在使用本地注册时,还可以直接提供返回Promise的函数
import { createApp, defineAsyncComponent } from 'vue'

createApp({
  // ...
  components: {
    AsyncComponent: defineAsyncComponent(() =>
      import('./components/AsyncComponent.vue')
    )
  }
})
对于高级用法,defineAsyncComponent可以接受一个对象
const AsyncComp = defineAsyncComponent({
  // 工厂函数
  loader: () => import('./Foo.vue')
  // 加载异步组件时使用的组件
  loadingComponent: LoadingComponent,
  //加载失败的时候使用的组件
  errorComponent: ErrorComponent,
  // 在显示加载组件之前延迟。默认值:200 ms。
  delay: 200,
  // 如果超时,将显示错误组件
  // 存在timeout并且超过这个时间. 默认值:无穷
  timeout: 3000,
  // 返回布尔值的函数,指示当加载器promise rejects时异步组件是否应该重试
  retryWhen: error => error.code !== 404,
  // 允许的最大重试次数
  maxRetries: 3,
  // 定义组件是否可挂载
  suspensible: false
})

resolveComponent

警告resolveComponent只能在render或setup函数中使用。
允许通过名称解析组件,如果它在当前应用程序实例中可用。如果找不到组件,返回组件或未定义组件
如果找不到组件,返回组件或未定义组件【组件】
app.component('MyComponent', {
  /* ... */
})
const MyComponent = resolveComponent('MyComponent')
resolveDynamicComponent【解析活动的组件active】

resolveDynamicComponent只能在render或setup函数中使用。
允许使用与<component:is="">相同的机制来解析组件。
返回解析的组件或一个新创建的VNode以组件名称作为节点标记的。
如果没有找到组件,会发出警告

resolveDirective

警告resolveDirective只能在render或setup函数中使用。
允许通过名称解析指令,如果它在当前应用程序实例中可用。
返回一个Directive或 当没有找到的时候,返回undefined。
app.directive('highlight', {})
render(){
    const highlightDirective = resolveDirective('highlight')
}
withDirectives

警告withDirectives只能在render或setup函数中使用。
:::允许应用指令到VNode。返回一个带有应用指令的VNode。
const bar = resolveDirective('bar')

return withDirectives(h('div'), [
  [bar, this.y]
])
createRenderer *【待】
nextTick
将回调延迟到下一个DOM更新周期之后执行。在更改了一些数据以等待DOM更新之后立即使用它
setup() {
    const message = ref('Hello!')
    const changeMessage = async newMessage => {
      message.value = newMessage
      /*等待DOM更新*/
      await nextTick()
      console.log('Now DOM is updated')
    }
  }
实例方法methods
$watch
参数

{string | Function} source
{Function | Object} callback
{Object} [options]
{boolean} deep
{boolean} immediate

用法
观察组件实例上的响应式属性或computed函数的更改。使用回调获取到给定属性的新值和旧值。我们只能通过顶级data、prop或computed的属性名作为字符串的形式传递。对于更复杂的表达式或嵌套属性,使用函数代替。
例子
const app = Vue.createApp({
  data() {
    return {
      a: 1,
      b: 2,
      c: {
        d: 3,
        e: 4
      }
    }
  },
  created() {
    // 顶级属性名a
    this.$watch('a', (newVal, oldVal) => {
      // 做一些事
    })

    // 观察监视单个嵌套属性
    this.$watch(
      () => this.c.d,
      (newVal, oldVal) => {
        // 做一些事
      }
    )

    // 监控复杂表达式
    this.$watch(
      // 每当表达式`this.a + this.b`产生不同的结果时
      // 处理程序将被调用。这就好像我们在看computed属性
      // 而不定义计算属性本身
      () => this.a + this.b,
      (newVal, oldVal) => {
        // 做一些事
      }
    )
  }
})

当监视的值是对象或数组时,对其属性或元素的任何更改都不会触发监视程序,因为它们引用相同的对象/数组

const app = Vue.createApp({
  data() {
    return {
      article: {
        text: 'Vue is awesome!'
      },
      comments: ['Indeed!', 'I agree']
    }
  },
  created() {
    this.$watch('article', () => {
      console.log('Article changed!')
    })

    this.$watch('comments', () => {
      console.log('Comments changed!')
    })
  },
  methods: {
    // 这些方法不会触发观察者,因为我们仅更改了对象/数组的属性,
    // 并不是 Object/Array 本身
    changeArticleText() {
      this.article.text = 'Vue 3 is awesome'
    },
    addComment() {
      this.comments.push('New comment')
    },

    // 这些方法会触发观察者,因为我们完整替换了对象/数组
    changeWholeArticle() {
      this.article = { text: 'Vue 3 is awesome' }
    },
    clearComments() {
      this.comments = []
    }
  }
})
$watch返回一个取消监视的函数,该函数停止触发回调

const unwatch = vm.$watch('a', cb)
// later, teardown the watcher
unwatch()
Option: deep
检测对象内部嵌套的值更改,需要在options参数中传入deep: true。注意,侦听数组突变并不需要这样做。
vm.$watch('someObject', callback, {
  deep: true
})
vm.someObject.nestedValue = 123
// 触发回调
Option: immediate
在选项中传入immediate: true将立即用表达式的当前值触发回调
vm.$watch('a', callback, {
  immediate: true
})
// “callback”被立即触发,当前值为“a”
 请注意,使用immediate选项,您将无法在第一个回调调用中取消监视给定的属性。
//这个例子是错误的
const unwatch = vm.$watch(
  'value',
  function() {
    doSomething()
    unwatch()
  },
  { immediate: true }
)
如果你仍然想在回调中调用一个unwatch函数,你应该首先检查它的可用性
const unwatch = vm.$watch(
  'value',
  function() {
    doSomething()
    if (unwatch) {
      unwatch()
    }
  },
  { immediate: true }
)
$emit 【一致】
$forceUpdate【一致】
$nextTick【一致】
实例 property
vm.$data 【一致】
vm.$props 【一致】
vm.$el 【一致】
vm.$options 【一致】
vm.$parent 【一致】
vm.$root【一致】
vm.$slots 【一致】
vm.$refs 【一致】
vm.$attrs 【一致】

废弃:
vm.$children
vm.$slots
vm.$scopedSlots
vm.$isServer
vm.$listeners
选项 / 组合
mixins 【一致】
extends【一致】
provide / inject【一致】
parent【废弃】
setup【新增】
详情见上
选项 / 资源
directives【一致】
components【一致】
filters【废弃】
选项 / 数据
data【一致】
props【一致】
computed【一致】
methods【一致】
watch【一致】
emits【新增】
详情
可以从组件发出的自定义事件的list/hash。 它具有基于数组的简单语法和允许配置事件验证的替代的基于对象的语法。
在基于对象的语法中,每个属性的值可以为null或验证函数。 验证函数将接收传递给emit调用的其他参数。例如,如果调用this.emit调用的其他参数。 例如,如果调用this.emit调用的其他参数。例如,如果调用this.emit('foo',1),则foo的相应验证器将接收参数1。验证器函数应返回一个布尔值,以指示事件参数是否有效。
const app = Vue.createApp({})

// 数组语法
app.component('todo-item', {
  emits: ['check'],
  created() {
    this.$emit('check')
  }
})

// 对象语法
app.component('reply-form', {
  emits: {
    // 无效
    click: null,
    // 有效
    submit: payload => {
      if (payload.email && payload.password) {
        return true
      } else {
        console.warn(`Invalid submit event payload!`)
        return false
      }
    }
  }
})

提示 在emit选项中列出的事件将不会被组件的根元素继承。

vue都是函数

createApp

const app = createApp(App)
app.use(store)
app.use(router)
app.mount('#app')

传了两个属性

v-model:selectKeys = "selectKeys"
import {reactive,toRef } from 'vue
export default{
    setup(props,ctx){
    //默认执行一次

    //页面使用 state.selectKeys
        const state  = reactive({ //attr slots emit
            selectKeys:0
        })
    //1.直接使用
        return {
            selectKeys:state.selectKeys
        }

    //2.导出,页面上直接使用,数据响应式还带解构
        return {
            ...toRefs(state) 
        }

        onMounted(()=>{

        })
    }
}

监控路由变化

import {reactive,toRef,watch } from 'vue
import {useRoute} from 'vue-router'
export default{
    setup(props,ctx){
        const state  = reactive({ //attr slots emit
            selectKeys:0
        })
        //1.watch监控路由变化
        watch(()=>route.path,(newValue)=>{
            state.selectKeys = [newValue]
        })
        //2.computed监控路由变化
        const selectKeys = computed(()=>{
            return [route.path]
        })
        return {
            selectKeys
        }
    }
}

vuex

import {reactive,toRef,watch ,computed} from 'vue'
import {useRoute} from 'vue-router'
export default{
    setup(props,ctx){
        const route = userRoute()
        const store  = useStore()
        const state  = reactive({ //attr slots emit
            selectKeys:0
        })
        //1.watch监控路由变化
        watch(()=>route.path,(newValue)=>{
            state.selectKeys = [newValue]
        })
        //2.computed监控路由变化
        const selectKeys = computed(()=>{
            return [route.path]
        })

        //ref 把普通值变成包装后的结构,将属性变成响应式
        // ref(store.getters.allTime)

        return {
            selectKeys,
            allTime:ref(store.getters.allTime)
        }
    }
}

//store.js
import {createStore} from 'vuex
export default {
    state:{

    },
    getters:{
        allTime:()=>{
            return 0
        }
    },
    mutations:{

    },
    actions:{

    },
    modules:{

    }

}

组件通信

import {reactive,toRef,watch ,computed} from 'vue'
import {useRoute} from 'vue-router'
import moment from 'moment'
export default{
    setup(props,ctx){
        const state = reactive({
            form:{
                date:moment(Date.now()).format('YYYY-MM-DD')
            }
        })

        //方法函数
        const onSubmit =()=>{
            //传给父组件
            this.$emit('handlePlan',state.form)
        }
        return {
            ...toRefs(state),
            onSubmit
        }
    }
}

//父组件
<Child @handlePlan="handlePlan" />

import {reactive,toRef,watch ,computed} from 'vue'
import {useRoute} from 'vue-router'
import moment from 'moment'
export default{
    setup(props,ctx){
        const state = reactive({
            form:{
                date:moment(Date.now()).format('YYYY-MM-DD')
            }
        })

        const handlePlan = (plan)=>{
            console.log(plan)
        }

        return {
            handlePlan
        }
    }
}

环境变量

VUE_APP_URL = 'http://www.xxx.com:3000'

封装api

import axios from 'axios
 const instance  = axios.create({
     baseURL:process.env.VUE_APP_URL,
     timeout:3000
 })

instance.interceptors.request.use((config)=>{
    return config
})

instance.interceptors.response.use((res)=>{
    return res.data.data
},err=>{
    return Promise.reject(err)
})

export function request(opts){
    return instance(opts)
}

//request.js
import {request } from '../utils/axios'

export function getPlanList(){
    return request({url:'/plan',method:'get'})
}

export function addPlan(data){
    return request({url:'/plan',method:'post',data})
}

export function deletePlan(){
    return request({url:'/plan',method:'delete',params:{id}})
}


//action_type.js
export const SET_PLAN_LIST = 'SET_PLAN_LIST'
export const ADD_PLAN = 'ADD_PLAN'
export const DELETE_PLAN = 'DELETE_PLAN'

//store.js
import {createStore} from 'vuex'
export * as types from './action_type'
import * as api from './request'
export default {
    state:{

    },
    getters:{
        allTime:()=>{
            return 0
        }
    },
    mutations:{
        [type.ADD_PLAN](state,payload){
           state.planList = [...state.planList,payload]
        },
        [type.DELETE_PLAN](state,payload){
            state.planList.filter(item=>{
                return item._id !=payload._id
            })
        },
        [type.SET_PLAN_LIST](state,payload){
            
        },
    },
    actions:{
        //restful api根据不同方法返回不同的资源
        async [type.ADD_PLAN]({commit},payload){
            let plan = await api.addPlan(payload)
            commit(type.ADD_PLAN,plan)
        },
        async [type.DELETE_PLAN]({commit},payload){
            let plan = await api.deletePlan(payload)
            commit(type.DELETE_PLAN,plan)
        },
        async [type.SET_PLAN_LIST]({commit},payload){
            let plan = await api.getPlanList(payload)
            commit(type.SET_PLAN_LIST,plan)
        },
    },
    modules:{

    }

}

使用数据

import {reactive,toRef,watch ,onMounted,onUpdated,compile,computed} from 'vue'
import {useStore} from 'vuex'
import moment from 'moment'
import * as types from '@/store/action_types'
export default{
    setup(props,ctx){
        const store = useStore()
        // const state = reactive({
        //     planList:store.state.planList //这样取的是默认值
        // })
        onMounted(()){
            store.dispatch(types.SET_PLAN_LIST)
        }
        //时间格式化方法
        const formatDate = (value)=>{
            return moment(value).format('YYYY-MM-DD')
        }
        return {
            ...toRefs(state.store),
            formatDate
        }
    }
}

简版vue

//1.创建虚拟节点,将虚拟节点转化为真实节点
//2.组件的实现 setup
//3.reactive api实现effect
//4.diff算法
//5.vite

let { render} = Vue
const state = {
    count:0
}
    const vnode  = {
        tag:'div',
        props:{color:'red'},
        children:[
            {
                tag:'p',
                props:{color:'blue},
                children:[
                    'vue@3-计数器'
                ]
            },
            {
                tag:'p',
                props:{
                    onClick:()=>{
                        alert(state.count)
                    }
                }
                children:[
                    'vue@3-计数器'
                ]
            }
        ]
    }
    render(vnode,app)

export function render(vnode,container){
    // 渲染页面的方法叫patch

    //1.第一次渲染 2.dom-diff
    patch(null,vnode,container)
}
/**
* n1 老的虚拟节点
* n2 新的虚拟节点
* container 容器
*/
function patch(n1,n2,container){
    //组件的虚拟节点是一个对象,tag是一个对象
    //如果是组件,tag可能是个对象
    //后续diff可以执行这个方法
    if(typeof n2.tag ==='string'){
        //标签
        mountElement(n2,container)
    }else if(typeof n2.tag==='object'){
        
    }
}

function mountElement(vnode,container){
    const  { tag,children,props }  = vnode
    //虚拟节点和真实节点做映射关系
    let el = (vnode.el =  nodeOps.createElement(tag))

    if(Array.isArray(children)){
        mountChild(children,el)
    }else{
        nodeOps.hostSetElementText(el,children)
    }
    container.insert(el,container)
}

function mountChild(children,container){
    for(var i=0;i<children.length;i++){
        let child = children[i]
        patch(null,child,container)
    }
}


//节点操作方法
exoprt const nodeOps = {
    //插入元素节点
    insert(child,parent,anchor){
        if(anchor){
            parent.insertBefore(child,anchor)
        }else{
            parent.appendChild(child)
        }
    },
    //移除节点
    remove(child){
        const parent  = child.parentNode;
        parent && parent.removeChild(child)
    },
    //创建节点
    createElement(tag){
        return document.createElement(tag)
    },
    //设置文本内容
    hostSetElementText(el,text){
        el.textContent = text
    }
}

1.Vue3 尝鲜
1.Vue3文档【Vue2迁移Vue3】

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345