细胞de理想

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

Vue3

0、前言

vue3的优化

 

  1. 对虚拟 DOM 进行了重写、模板编译速度的提升,对静态数据的跳过处理
  2. 对数组的监控
  3. 对ts有了很好的支持
  4. 对2.x版本的完全兼容
  5. 可以有多个根节点(也有bug,比如外层开了display:flex 那么里面会收到影响,也就是说布局更灵活但也更要小心,总之请对自己与他人的代码负责) 6. 支持Source map,虽然没演示但是这点真的重要 7. vue2 大量的 API 挂载在 Vue 对象的原型上,难以实现 TreeShaking

使用CompositionAPI理由

  • 更好的Typescript支持
  • 在复杂功能组件中可以实现根据特性组织代码 - 代码内聚性。比如:排序和搜索逻辑内聚
  • 组件间代码复用
经过了漫长的迭代,Vue 3.0 终于在上 2020-09-18 发布了,带了翻天覆地的变化,使用了 Typescript 进行了大规模的重构,带来了 Composition API RFC 版本,类似 React Hook 一样的写 Vue,可以自定义自己的 hook ,让使用者更加的灵活。
  1. setup()
  2. ref()
  3. reactive()
  4. isRef()
  5. toRefs()
  6. computed()
  7. watch()
  8. LifeCycle Hooks(新的生命周期)
  9. Template refs
  10. globalProperties
  11. Suspense

1、setup 函数

setup() 函数是 vue3 中,专门为组件提供的新属性。

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

setup(props, context) {
    context.attrs
    context.slots
    context.parent
    context.root
    context.emit
    context.refs
    return {
    }
  }

 

  • props: 用来接收 props 数据
  • context 用来定义上下文, 上下文对象中包含了一些有用的属性,这些属性在 vue 2.x 中需要通过 this 才能访问到,在 setup() 函数中无法访问到 this,是个 undefined
  • 返回值:return {},返回响应式数据,模版中需要使用的函数
  • 在以下方法前执行:
  1. Components
  2. Props
  3. Data
  4. Methods
  5. Computed Properties
  6. Lifecycle methods

2、reactive 函数

reactive() 函数接收一个普通对象,返回一个响应式的数据对象,想要使用创建的响应式数据也很简单,创建出来之后,在 setup 中 return 出去,在 template 中调用即可

<template>
  {{state.name}} // test
<template>
<script>
import { defineComponent, reactive, ref, toRefs } from 'vue';
export default defineComponent({
  setup(props, context) {
    let state = reactive({
      name: 'test'
    });
    return state
  }
});
</script>

3、ref() 函数

ref() 函数用来根据给定的值创建一个响应式的数据对象,ref() 函数调用的返回值是一个对象,这个对象上只包含一个 value 属性,只在 setup 函数内部访问 ref 函数需要加 .value

<template>
    <div class="mine">
        {{count}} // 10
    </div>
</template>
<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
  setup() {
    const count = ref(10)
    // 在js 中获取ref 中定义的值, 需要通过value属性
    console.log(count.value);
    return {
       count
    }
   }
});
</script>

在 reactive 对象中访问 ref 创建的响应式数据

<template>
    <div class="mine">
        {{count}} -{{t}} // 10 -100
    </div>
</template>
<script>
import { defineComponent, reactive, ref, toRefs } from 'vue';
export default defineComponent({
  setup() {
    const count = ref<number>(10)
    const obj = reactive({
      t: 100,
      count
    })
    // 通过reactive 来获取ref 的值时,不需要使用.value属性
    console.log(obj.count);
    return {
       ...toRefs(obj)
    }
   }
});
</script>

4、isRef() 函数

 

isRef() 用来判断某个值是否为 ref() 创建出来的对象

 

<script>
import { defineComponent, isRef, ref } from 'vue';
export default defineComponent({
  setup(props, context) {
    const name = 'vue'
    const age = ref(18)
    console.log(isRef(age)); // true
    console.log(isRef(name)); // false
    return {
      age,
      name
    }
  }
});
</script>

5、toRefs() 函数

 

toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据

 

<template>
  <div class="mine">
    {{name}} // test
    {{age}} // 18
  </div>
</template>
<script>
import { defineComponent, reactive, ref, toRefs } from 'vue';
export default defineComponent({
  setup(props, context) {
    let state = reactive({
      name: 'test'
    });
    const age = ref(18)
    return {
      ...toRefs(state),
      age
    }
  }
});
</script>

6、computed()

该函数用来创造计算属性,和过去一样,它返回的值是一个 ref 对象。

里面可以传方法,或者一个对象,对象中包含 set()、get()方法。

<script>
import { computed, defineComponent, ref } from 'vue';
export default defineComponent({
  setup(props, context) {
    const age = ref(18)
    // 根据 age 的值,创建一个响应式的计算属性 readOnlyAge,它会根据依赖的 ref 自动计算并返回一个新的 ref
    const readOnlyAge = computed(() => age.value++) // 19
    return {
      age,
      readOnlyAge
    }
  }
});
</script>

 

6.2 通过 set()、get()方法创建一个可读可写的计算属性

 

<script>
import { computed, defineComponent, ref } from 'vue';
export default defineComponent({
  setup(props, context) {
    const age = ref(18)
    const computedAge = computed({
      get: () => age.value + 1,
      set: value => age.value + value
    })
    // 为计算属性赋值的操作,会触发 set 函数, 触发 set 函数后,age 的值会被更新
    age.value = 100
    return {
      age,
      computedAge
    }
  }
});
</script>

7、 watch() 函数

watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。

默认情况是懒执行的,也就是说仅在侦听的源数据变更时才执行回调。

 

7.1 监听用 reactive 声明的数据源

 

<script>
import { computed, defineComponent, reactive, toRefs, watch } from 'vue';

export default defineComponent({
  setup(props, context) {
    const state = reactive({ name: 'vue', age: 10 })
    watch(
      () => state.age,
      (age, preAge) => {
        console.log(age); // 100
        console.log(preAge); // 10
      }
    )
    // 修改age 时会触发 watch 的回调, 打印变更前后的值
    state.age = 100
    return {
      ...toRefs(state)
    }
  }
});
</script>

7.2 监听用 ref 声明的数据源

<script>
import { defineComponent, ref, watch } from 'vue';
export default defineComponent({
  setup(props, context) {
    const age = ref(10);
    watch(age, () => console.log(age.value)); // 100
    // 修改age 时会触发watch 的回调, 打印变更后的值
    age.value = 100
    return {
      age
    }
  }
});
</script>

7.3 同时监听多个值

 

<script>
import { computed, defineComponent, reactive, toRefs, watch } from 'vue';

export default defineComponent({
  setup(props, context) {
    const state = reactive({ name: 'vue', age: 10 })
    watch(
      [() => state.age, () => state.name],
      ([newName, newAge], [oldName, oldAge]) => {
        console.log(newName);
        console.log(newAge);
        console.log(oldName);
        console.log(oldAge);
      }
    )
    // 修改age 时会触发watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调
    state.age = 100
    state.name = 'vue3'
    return {
      ...toRefs(state)
    }
  }
});
</script>

 

7.4 stop 停止监听

 

在 setup() 函数内创建的 watch 监视,会在当前组件被销毁的时候自动停止。

如果想要明确地停止某个监视,可以调用 watch() 函数的返回值即可,语法如下:

 

<script>
import { set } from 'lodash';
import { computed, defineComponent, reactive, toRefs, watch } from 'vue';
export default defineComponent({
  setup(props, context) {
    const state = reactive({ name: 'vue', age: 10 })
    const stop =  watch(
      [() => state.age, () => state.name],
      ([newName, newAge], [oldName, oldAge]) => {
        console.log(newName);
        console.log(newAge);
        console.log(oldName);
        console.log(oldAge);
      }
    )
    // 修改age 时会触发 watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调
    state.age = 100
    state.name = 'vue3'
    setTimeout(()=> {
      stop()
      // 此时修改时, 不会触发watch 回调
      state.age = 1000
      state.name = 'vue3-'
    }, 1000) // 1秒之后讲取消watch的监听
    return {
      ...toRefs(state)
    }
  }
});
</script>

 

8、LifeCycle Hooks(新的生命周期)

新版的生命周期函数,可以按需导入到组件中,且只能在 setup() 函数中使用,但是也可以在 setup 外定义,在 setup 中使用

 

 

 

 

                                                                                      onRenderTriggered

 

<script>
import { set } from 'lodash';
import { defineComponent, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onErrorCaptured, onMounted, onUnmounted, onUpdated } from 'vue';
export default defineComponent({
  setup(props, context) {
    onBeforeMount(()=> {
      console.log('beformounted!')
    })
    onMounted(() => {
      console.log('mounted!')
    })
    onBeforeUpdate(()=> {
      console.log('beforupdated!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onBeforeUnmount(()=> {
      console.log('beforunmounted!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
    onErrorCaptured(()=> {
      console.log('errorCaptured!')
    })
    return {}
  }
});
</script>

 

 

 

9、Template refs

通过 refs 来回去真实 dom 元素,这个和 react 的用法一样,为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup()中声明一个 ref 并返回它

<div>
    <div ref="content">第一步, 在dom上面定义, 他会有一个回调</div>
  </div>
  <ul>
    <li>v-for 出来的ref</li>
    <li>可以写为表达式的形式, 可以推导出vue是如何实现的</li>
    <li>vue2.x的时候v-for不用这么麻烦, 直接写上去会被组装成数组</li>
    <li :ref="el => { items[index] = el }" v-for="(item,index) in 6" :key="item">{{item}}</li>
  </ul>
import { ref, onMounted, onBeforeUpdate } from "vue";

export default {
  setup() {
    // 2: 定义一个变量接收dom, 名字无所谓, 但是与dom统一的话会有很好的语义化
    const content = ref(null);
    const items = ref([]);

    // 4: 在生命周期下, 这个值已经完成了变化, 所以当然第一时间就拿到
    onMounted(() => {
      console.log(content.value);
      console.log("li标签组", items.value);
    });

    // 5: 确保在每次变更之前重置引用
    onBeforeUpdate(() => {
      items.value = [];
    });

    // 3: 返出去的名称要与dom的ref相同, 这样就可以接收到dom的回调
    return {
      content,
      items
    };
  }
};

 

10、vue 的全局配置

 

通过 vue 实例上 config 来配置,包含 Vue 应用程序全局配置的对象。

您可以在挂载应用程序之前修改下面列出的属性:

const app = Vue.createApp({})
app.config = {...}

为组件渲染功能和观察程序期间的未捕获错误分配处理程序。

错误和应用程序实例将调用处理程序:

 

app.config.errorHandler = (err, vm, info) => {}

 

可以在应用程序内的任何组件实例中访问的全局属性,组件的属性将具有优先权。

这可以代替 Vue 2.xVue.prototype  扩展:

 

const app = Vue.createApp({})
app.config.globalProperties.$http = 'xxxxxxxxs'

可以在组件用通过 getCurrentInstance()  来获取全局 globalProperties  中配置的信息,getCurrentInstance()  方法获取当前组件的实例,然后通过 ctx 属性获得当前上下文,这样我们就能在 setup 中使用 router 和 vuex,通过这个属性我们就可以操作变量、全局属性、组件属性等等。

setup( ) {
  const { ctx } = getCurrentInstance();
  ctx.$http
}

11、Suspense 组件

在开始介绍 Vue 的 Suspense 组件之前,我们有必要先了解一下 React 的 Suspense 组件,因为他们的功能类似。

React.lazy 接受一个函数,这个函数需要动态调用 import()。

它必须返回一个 Promise,该 Promise 需要 resolve 一个 default export 的 React 组件。

 

import React, { Suspense } from 'react';
const myComponent = React.lazy(() => import('./Component'));
function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <myComponent />
      </Suspense>
    </div>
  );
}

Vue3 也新增了 React.lazy 类似功能的 defineAsyncComponent 函数,处理动态引入(的组件)。

defineAsyncComponent 可以接受返回承诺的工厂函数。

当您从服务器检索到组件定义时,应该调用 Promise 的解析回调。

您还可以调用 reject(reason) 来指示负载已经失败。

import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
  import('./components/AsyncComponent.vue')
)
app.component('async-component', AsyncComp)

 

Vue3 也新增了 Suspense 组件:

 

<template>
  <Suspense>
    <template #default>
      <my-component />
    </template>
    <template #fallback>
      Loading ...
    </template>
  </Suspense>
</template>
<script>
 import { defineComponent, defineAsyncComponent } from "vue";
 const MyComponent = defineAsyncComponent(() => import('./Component'));
export default defineComponent({
   components: {
     MyComponent
   },
   setup() {
     return {}
   }
})
</script>

12、vuex, router, vue 初始化写法的变化

vue

import { createApp } from 'vue';
import App from './App.vue'
import router from './router'
import store from './store'

// 方法一. 创建实例变成了链式, 直接写下去感觉语义与结构有点模糊, 但是我们要理解vue这样做的良苦用心, 前端趋近于函数化。
// createApp(App).use(router).use(store).mount('#app')

// 方法二.
const app = createApp(App);
app.use(router);
app.use(store);
app.mount('#app');

router

import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  }
]

const router = createRouter({
  // 专门创建history的函数
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

使用:

<template>
  <div>
    {{id}}
  </div>
</template>

<script>
import { getCurrentInstance, ref } from 'vue';
export default {
  setup(){
    const { ctx } = getCurrentInstance()
    // 1. 这样也是为了去掉this
    // 2. 方便类型推导
    console.log(ctx.$router); // push等方法
    console.log(ctx.$router.currentRoute.value); // 路由实例
    // 这个其实没有必要变成ref因为这个值没必要动态
    // 但是他太长了, 这个真的不能忍
    const id = ref(ctx.$router.currentRoute.value.query.id)

    // 4: 页面拦截器
    ctx.$router.beforeEach((to, from,next)=>{
      console.log('路由的生命周期')
      next()
    })
    return {
      id
    }
  }
}
</script>

 

vuex

import { createStore } from 'vuex'
// 专门创建实例的一个方法
export default createStore({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
});

使用:

import { createStore } from 'vuex'

// 难道前端趋势只有函数这一种吗
export default createStore({
  state: {
    name:'牛逼, 你拿到我了',
    age: 24,
    a:'白',
    b:'黑'
  },
  mutations: {
    updateName(state, n){
      state.name += n
    }
  },
  actions: {
    deferName(store) {
     setTimeout(()=>{
       // 必须只有commit可以修改值, 这个设定我比较反对, 可以讨论
       // vuex本身结构就很拖沓, 定义域使用个人都不喜欢
      store.state.name = '牛逼, 你改回来了'
     },1000)
    }
  },
  getters: {
    fullName(state){ return `${state.a} - + -${state.b}` }
  },
  modules: {
  }
});

 

<template>
  <div>
    <p>{{name}}</p>
    <button @click="updateName('+')">点击改变名字</button>
    

    <button @click="deferName('+')">改回来</button>

    <p>{{fullName}}</p>
  </div>
</template>

<script>
import { useStore } from "vuex";
import { computed } from "vue";
export default {
  setup() {
    const store = useStore();
    // 1: 单个引入
    const name = computed(() => store.state.name);
    // 2: 引入整个state
    const state = computed(() => store.state);
    console.log("vuex的实例", state.value); // 别忘了.value

    // 3: 方法其实就直接从本体上取下来了
    const updateName = newName => store.commit("updateName", newName);

    // 4: action一个意思
    const deferName = () => store.dispatch("deferName");

    // 5: getter 没变化
    const fullName = computed(() => store.getters.fullName);
    return {
      name,
      fullName,
      deferName,
      updateName,
    };
  }
};
</script>

 

13、组件注入

父组件:

<template>
  <div>
    组件:
    <zj :type="type" @ok="wancheng"></zj>
  </div>
</template>

<script>
import zj from "../components/子组件.vue";
import { ref } from 'vue';
import { provide } from 'vue'

export default {
  components: { 
    zj
  },
  setup() {
    provide('name','向下传值'); // 基础值
    provide('name2', ref('向下传值')); // 监控值
    const type = ref('大多数');

    function wancheng(msg){
      console.log('子组件-->',msg)
      setTimeout(()=>{
        type.value = 'xxxxxxx'
      },2000)
    }
    return {
      type,
      wancheng
    }
  }
};
</script>

 

子组件

 

 

<template>
  <div>props的属性不用setup去return --- {{type}}</div>
</template>

<script>
import { inject, ref } from 'vue'
export default {
  props: {
    type: String
  },
  // 1: props也是不可以解构的, 会失去响应式
  // 2: context是上下文, 我们可以获取到slots emit 等方法
  // 3: props, context 分开也是为了ts更明确的类型推导
  // setup({type}){
  setup(props, context) {
    // 1: props
    console.log("props", props.type);
    console.log("上下文", context);
    context.emit('ok','传递完成')

    // 2: 注入
    console.log('inject',inject('name'));
    console.log('inject',inject('xxxx','我是默认值'))
    inject('name1', ref('默认值')) // 接收方也可以这样
  }
};
</script>

14、vue 3.x 完整组件模版结构

一个完成的 vue 3.x 完整组件模版结构包含了:组件名称、 props、components、setup(hooks、computed、watch、methods 等)

 

<template>
  <div class="mine" ref="elmRefs">
    <span>{{name}}</span>
    <br>
    <span>{{count}}</span>
    <div>
      <button @click="handleClick">测试按钮</button>
    </div>
    <ul>
      <li v-for="item in list" :key="item.id">{{item.name}}</li>
    </ul>
  </div>
</template>
<script lang="ts">
import { computed, defineComponent, getCurrentInstance, onMounted, PropType, reactive, ref, toRefs } from 'vue';
interface IState {
  count: 0,
  name: string,
  list: Array<object>
}
export default defineComponent({
  name: 'demo',
  // 父组件传子组件参数
  props: {
    name: {
      type: String as PropType<null | ''>,
      default: 'vue3.x'
    },
    list: {
      type: Array as PropType<object[]>,
      default: () => []
    }
  },
  components: {
    /// TODO 组件注册
  },
  emits: ["emits-name"], // 为了提示作用
  setup (props, context) {
    console.log(props.name)
    console.log(props.list)
    const state = reactive<IState>({
      name: 'vue 3.0 组件',
      count: 0,
      list: [
        {
          name: 'vue',
          id: 1
        },
        {
          name: 'vuex',
          id: 2
        }
      ]
    })
    const a = computed(() => state.name)
    onMounted(() => {
    })
    function handleClick () {
      state.count ++
      // 调用父组件的方法
      context.emit('emits-name', state.count)
    }
    return {
      ...toRefs(state),
      handleClick
    }
  }
});
</script>

 

posted on 2022-03-22 15:26  细胞的理想  阅读(335)  评论(0编辑  收藏  举报