关于javascript:vue3实战笔记-快速入门????

前言

vue3正式版曾经公布好几个月了。置信有不少人早已蠢蠢欲动,这里依据这几天的我的项目教训列举几点在我的项目中可能用到的知识点跟大家分享总结,在开展性能点介绍之前,先从一个简略的demo帮忙大家能够疾速动手新我的项目????

案例????

在正式介绍之前,大家能够先跑一下这个 demo 疾速相熟用法

<template>
  <div>
    <el-button type="primary" @click="handleClick">{{
      `${vTitle}${state.nums}-${staticData}`
    }}</el-button>
    <ul>
      <li v-for="(item, index) in state.list" :key="index"> {{ item }} </li>
    </ul>
  </div>
</template>

<script lang="ts">
  import { defineComponent, reactive, ref, watch, onMounted, computed, nextTick } from 'vue';
  interface State {
    nums: number;
    list: string[];
  }

  export default {
    setup() {
      const staticData = 'static';
      let title = ref('Create');
      const state = reactive<State>({
        nums: 0,
        list: [],
      });

      watch(
        () => state.list.length,
        (v = 0) => {
          state.nums = v;
        },
        { immediate: true }
      );
      const vTitle = computed(() => '-' + title.value + '-');

      function handleClick() {
        if (title.value === 'Create') {
          title.value = 'Reset';
          state.list.push('小黑');
        } else {
          title.value = 'Create';
          state.list.length = 2;
        }
      }

      const getList = () => {
        setTimeout(() => {
          state.list = ['小黄', '小红'];
        }, 2000);
        nextTick(() => {
          console.log('nextTick');
        });
      };

      onMounted(() => {
        getList();
      });

      return {
        staticData,
        state,
        handleClick,
        title,
        vTitle,
      };
    },
  };
</script>

成果如下????

vue3生命周期

vue3的生命周期函数只能用在setup()里应用,变动如下????

vue2 vue3
beforeCreate setup
created setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated
deactivated onDeactivated

扩大

  1. 能够看进去vue2的beforeCreatecreated变成了setup
  2. 绝大部分生命周期都是在本来vue2的生命周期上带上了on前缀

应用

在setup中应用生命周期:

import {  onMounted } from 'vue';

export default {
  setup() {
    onMounted(() => {
      // 在挂载后申请数据
      getList();
    })
  }
};

vue3罕用api

上述案例中应用了一些罕用的api,上面带大家一一意识下咱们的新敌人

setup()

setup函数是一个新的组件选项。作为在组件内应用Composition API的入口点。从生命周期钩子的视角来看,它会在beforeCreate钩子之前被调用,所有变量、办法都在setup函数中定义,之后return进来供内部应用

该函数有2个参数:

  • props
  • context

其中context是一个上下文对象,具备属性(attrsslotsemitparentroot),其对应于vue2中的this.$attrsthis.$slotsthis.$emitthis.$parentthis.$root

setup也用作在tsx中返回渲染函数:

  setup(props, { attrs, slots }) {
    return () => {
      const propsData = { ...attrs, ...props } as any;
      return <Modal {...propsData}>{extendSlots(slots)}</Modal>;
    };
  },
*留神:this关键字在setup()函数外部不可用,在办法中拜访setup中的变量时,间接拜访变量名就能够应用。

扩大

为什么props没有被蕴含在上下文中?

  1. 组件应用props的场景更多,有时甚至只须要应用props
  2. 将props独立进去作为一个参数,能够让TypeScript对props独自做类型推导,不会和上下文中其余属性混同。这也使得setup、render和其余应用了TSX的函数式组件的签名保持一致。

reactive, ref

reactiveref都是vue3中用来创立响应式数据的api,作用等同于在vue2中的data,不同的是他们应用了ES6Porxy API解决了vue2 defineProperty 无奈监听数组和对象新增属性的痛点

用法

<template>
  <div class="contain">
    <el-button type="primary" @click="numadd">add</el-button>
    <span>{{ `${state.str}-${num}` }}</span>
  </div>
</template>

<script lang="ts">
  import { reactive, ref } from 'vue';
  interface State {
    str: string;
    list: string[];
  }

  export default {
    setup() {
      const state = reactive<State>({
        str: 'test',
        list: [],
      });
      //ref须要加上value能力获取
      const num = ref(1);
      const numadd = () => {
        num.value++;
      };
      return { state, numadd, num };
    },
  };
</script>

成果如下????

区别

  • 应用时在setup函数中须要通过外部属性.value来拜访ref数据,return进来的ref可间接拜访,因为在返回时曾经主动解套;reactive能够间接通过创建对象拜访
  • ref承受一个参数,返回响应式ref对象,个别是根本类型值(StringNmuberBoolean 等)或单值对象。如果传入的参数是一个对象,将会调用 reactive 办法进行深层响应转换(此时拜访ref中的对象会返回Proxy对象,阐明是通过reactive创立的);援用类型值(ObjectArray)应用reactive

toRefs

将传入的对象里所有的属性的值都转化为响应式数据对象(ref)

应用reactive return 进来的值每个都须要通过reactive对象 .属性的形式拜访十分麻烦,咱们能够通过解构赋值的形式范畴,然而间接解构的参数不具备响应式,此时能够应用到这个api(也能够对props中的响应式数据做此解决)

将后面的例子作如下????批改应用起来更加不便:

<template>
  <div class="contain">
    <el-button type="primary" @click="numadd">add</el-button>
-    <span>{{ `${state.str}-${num}` }}</span>
+    <span>{{ `${str}-${num}` }}</span>
  </div>
</template>

<script lang="ts">
  import { reactive, ref, toRefs } from 'vue';
  interface State {
    str: string;
    list: string[];
  }

  export default {
    setup() {
      const state = reactive<State>({
        str: 'test',
        list: [],
      });
      //ref须要加上value能力获取
      const num = ref(1);
      const numadd = () => {
        num.value++;
      };
-      return { state, numadd, num };
+      return { ...toRefs(state), numadd, num };
    },
  };
</script>

toRef

toRef 用来将援用数据类型或者reavtive数据类型中的某个值转化为响应式数据

用法

  • reactive数据类型
     /* reactive数据类型 */
      let obj = reactive({ name: '小黄', sex: '1' });
      let state = toRef(obj, 'name');

      state.value = '小红';
      console.log(obj.name); // 小红
      console.log(state.value); // 小红

      obj.name = '小黑';
      console.log(obj.name); // 小黑
      console.log(state.value); // 小黑
  • 援用数据类型
<template>
  <span>ref----------{{ state1 }}</span>
  <el-button type="primary" @click="handleClick1">change</el-button>
  <!-- 点击后变成小红 -->
  <span>toRef----------{{ state2 }}</span>
  <el-button type="primary" @click="handleClick2">change</el-button>
  <!-- 点击后还是小黄 -->
</template>

<script>
  import { ref, toRef, reactive } from 'vue';
  export default {
    setup() {
      let obj = { name: '小黄' };
      const state1 = ref(obj.name); // 通过ref转换
      const state2 = toRef(obj, 'name'); // 通过toRef转换
      
      const handleClick1 = () => {
        state1.value = '小红';
        console.log('obj:', obj); // obj:小黄
        console.log('ref', state1); // ref:小红
      };
      
      const handleClick2 = () => {
        state2.value = '小红';
        console.log('obj:', obj); // obj:小红
        console.log('toRef', state2); // toRef:小红
      };
      return { state1, state2, handleClick1, handleClick2 };
    },
  };
</script>

小结

  • ref 是对原数据的拷贝,响应式数据对象值扭转后会同步更新视图,不会影响到原始值。
  • toRef 是对原数据的援用,响应式数据对象值扭转后不会扭转视图,会影响到原始值。

isRef

判断是否是ref对象,外部是判断数据对象上是否蕴含__v_isRef属性且值为true。

    setup() {
      const one = ref(0);
      const two = 0;
      const third = reactive({
        data: '',
      });
      let four = toRef(third, 'data');
      const { data } = toRefs(third);
      
      console.log(isRef(one)); // true
      console.log(isRef(data)); // true
      console.log(isRef(four)); // true
      console.log(isRef(two)); // false
      console.log(isRef(third)); // false
    }

unref

如果参数为ref,则返回外部原始值,否则返回参数自身。外部是val = isRef(val) ? val.value : val的语法糖。

    setup() {
      const hello = ref('hello');
      console.log(hello); // { __v_isRef: true,value: "hello"... }
      const newHello = unref(hello);
      console.log(newHello); // hello
    }

watch, watchEffect

watch

watch侦听器,监听数据变动

用法和vue2有些区别

语法为:watch(source, callback, options)

  • source:用于指定监听的依赖对象,能够是表达式,getter函数或者蕴含上述两种类型的数组(如果要监听多个值)
  • callback:依赖对象变动后执行的回调函数,带有2个参数:newValoldVal。如果要监听多个数据每个参数能够是数组 [newVal1, newVal2, ... newValN][oldVal1, oldVal2, ... oldValN]
  • options:可选参数,用于配置watch的类型,能够配置的属性有 immediate(立刻触发回调函数)、deep(深度监听)
      let title = ref('Create');
      let num = ref(0);
      const state = reactive<State>({
        nums: 0,
        list: [],
      });
      
      // 监听ref
      watch(title, (newValue, oldValue) => {
         /* ... */
      });

      // 监听reactive
      watch(
        // getter
        () => state.list.length,
        // callback
        (v = 0) => {
          state.nums = v;
        },
         // watch Options
        { immediate: true }
      );
      
      // 监听多个ref
      watch([title, num], ([newTitle, newNum], [oldTitle, oldNum]) => {
        /* ... */
      });      
      
      // 监听reactive多个值
      watch([() => state.list, () => state.nums], ([newList, newNums], [oldList, oldvNums]) => {
        /* ... */
      });

咱们能够向下面一样将多个值的监听拆成多个对单个值监听的watch。这有助于咱们组织代码并创立具备不同选项的观察者;watch办法会返回一个stop()办法,若想要进行监听,便可间接执行该stop函数
watchEffect
立刻执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更是从新运行该函数.

<template>
  <div class="contain">
    <el-button type="primary" @click="numadd">add</el-button>
    <span>{{ num }}</span>
  </div>
</template>

<script lang="ts">
  import { ref, watchEffect } from 'vue';
  export default {
    setup() {
      const num = ref(1);
      const numadd = () => {
        num.value++;
      };

      watchEffect(() => {
        console.log(num.value); // 1,2,3...
      });
      return { numadd, num };
    },
  };
</script>

能够看到在组件初始化的时候该回调函数立刻执行了一次,同时开始自动检测回调函数外头依赖的值,并在依赖关系产生扭转时主动触发这个回调函数,这样咱们就不用手动传入依赖特意去监听某个值了

computed

传入一个getter函数,返回一个默认不可手动批改的ref对象.

    setup() {
      let title = ref('Create');
      const vTitle = computed(() => '-' + title.value + '-');
      
      function handleClick() {
        if (title.value === 'Create') {
          title.value = 'Reset';
        } else {
          title.value = 'Create';
        }
      }
      }

反转字符串:

setup() {
    const state = reactive({
      value: '',
      rvalue: computed(() =>
        state.value
          .split('')
          .reverse()
          .join('')
      )
    })
    return toRefs(state)
  }

provide, inject

provide()inject()用来实现多级嵌套组件之间的数据传递,父组件或先人组件应用 provide()向下传递数据,子组件或子孙组件应用inject()来接收数据

// 父组件
<script>
import {provide} from 'vue'
export default {
    setup() {
        const obj = ref('johnYu')
        // 向子孙组件传递数据provide(名称,数据)
        provide('name', obj)
    }
}
</script>

// 孙组件
<script>
import {inject} from 'vue'
export default {
    setup() {    
        // 接管父组件传递过去的数据inject(名称)
        const name = inject('name') // johnYu
        return {name}
    }
}
</script>

getCurrentInstance

getCurrentInstance办法用于获取以后组件实例,仅在setup和生命周期中起作用

import { getCurrentInstance, onBeforeUnmount } from 'vue';

const instance = getCurrentInstance();
// 判断以后组件实例是否存在
if (instance) {
    onBeforeUnmount(() => {
        /* ... */
     });
 }

通过instance中的ctx属性能够取得以后上下文,通过这个属性能够应用组件实例中的各种全局变量和属性

$Refs

为了取得对模板中元素或组件实例的援用,咱们能够同样应用ref并从setup()返回它

<template>
  <div ref="root">This is a root element</div>
</template>

<script>
  import { ref, onMounted } from 'vue'

  export default {
    setup() {
        // 获取渲染上下文的援用
      const root = ref(null)

      onMounted(() => {
        // 仅在首次渲染后能力取得指标元素
        console.log(root.value) // <div>This is a root element</div>
      })

      return {
        root
      }
    }
  }
</script>

.sync

在vue2.0中应用.sync实现prop的双向数据绑定,在vue3中将它合并到了v-model

vue2.0

    <el-pagination
      :current-page.sync="currentPage1"
    >
    </el-pagination>

vue3.0

    <el-pagination
      v-model:current-page="currentPage1"
    >
    </el-pagination>

v-slot

Child.vue

<template>
  <div class="child">
    <h3>具名插槽</h3>
    <slot name="one" />
    <h3>作用域插槽</h3>
    <slot :data="list" />
    <h3>具名作用域插槽</h3>
    <slot name="two" :data="list" />
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      list: ['zhangsan', 'lisi']
    }
  }
}
</script>

vue2用法

<template>
  <div>
    <child>
      <div slot="one">
        <span>菜单</span>
      </div>
      <div slot-scope="user">
        <ul>
          <li v-for="(item, index) in user.data" :key="index">{{ item }}</li>
        </ul>
      </div>
      <div slot="two" slot-scope="user">
        <div>{{ user.data }}</div>
      </div>
    </child>
  </div>
</template>

vue3用法

新指令v-slot对立slotslot-scope繁多指令语法。速记v-slot能够潜在地对立作用域和一般插槽的用法。

<template>
  <div>
    <child>
      <template v-slot:one>
        <div><span>菜单</span></div>
      </template>
      <template v-slot="user">
        <ul>
          <li v-for="(item, index) in user.data" :key="index">{{ item }}</li>
        </ul>
      </template>
      <template v-slot:two="user">
        <div>{{ user.data }}</div>
      </template>
      <!-- 简写 -->
      <template #two="user">
      <div>{{ user.data }}</div>
      </template>
    </child>
  </div>
</template>

Composition API 联合vuex4, Vue Router 4

createStore,useStore,useRouter,useRoute

vuex4中通过createStore创立Vuex实例,useStore能够获取实例,作用等同于vue2.0中的this.$store;

Vue Router 4 useRouter能够获取路由器,用来进行路由的跳转,作用等同于vue2.0的this.$router,useRoute就是钩子函数相当于vue2.0的this.$route

store/index.ts

import {createStore} from 'vuex';
const store = createStore({
  state: {
    user: null,
  },
  mutations: {
    setUser(state, user) {
      state.user = user;
    }
  },
  actions: {},
  modules: {}
});

router/index.ts

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { scrollBehavior } from './scrollBehaviour.ts';

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Home',
    component: () => import('/@/views/home.vue') // vite.config.vue中配置alias
  }
];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
  strict: true,
  scrollBehavior: scrollBehavior,
});

export default router;

main.ts

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

const app = createApp(App);
app.config.globalProperties.$getTime = getTime // vue3配置全局变量,取代vue2的Vue.prototype
app.use(store).use(router)
app.mount('#app');

App.vue

import { reactive } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import { ElMessage } from 'element-plus';
export default {
  name: "App",
  setup() {
    const store = useStore();
    const router = useRouter();
    // 用户名和明码
    const Form = reactive({
      username: "johnYu",
      password: "123456",
    });
    // 登录
    function handelLogin() {
      store.commit("setUser", {
        username: Form.username,
        password: Form.password,
      });
      ElMessage({
        type: 'success',
        message: '登陆胜利',
        duration: 1500,
      });
      // 跳转到首页
      router.push({
         name: 'Home',
         params: {
           username: Form.username
         },
      });
    }
    return {
      Form,
      handelLogin
      };
  }

home.vue

  import { useRouter, useRoute } from 'vue-router';
  import Breadcrumb from '/@/components/Breadcrumb.vue';

  export default defineComponent({
    name: 'Home',
    components: {
      Breadcrumb,
    },
    setup() {
      const route = useRoute();
      // 接管参数
      const username = route.params.username;
      return {username}
    }
    })

导航守卫

因为应用 Composition API 的起因,setup函数外面别离应用onBeforeRouteLeaveonBeforeRouteUpdate 两个新增的 API 代替vue2.0中的beforeRouteLeavebeforeRouteUpdate

  import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router';
   setup() {
      onBeforeRouteUpdate((to) => {
        if (to.name === 'Home'){
            /* ... */
        }
      });
   }

useLink

useLink它提供与router-linkv-slot API 雷同的拜访权限,将RouterLink的外部行为公开为Composition API函数,用于裸露底层的定制能力

<template>
  <div ref="root">This is a root element</div>
</template>

<script>
  import { computed } from 'vue';
  import { RouterLink, useLink } from 'vue-router';

  export default {
    name: 'AppLink',

    props: {
      ...RouterLink.props,
      inactiveClass: String,
    },

    setup(props) {
      const { route, href, isActive, isExactActive, navigate } = useLink(props);
      const isExternalLink = computed(
        () => typeof props.to === 'string' && props.to.startsWith('http')
      );

      return { isExternalLink, href, navigate, isActive };
    },
  };
</script>

插槽 prop 的对象蕴含上面几个属性:

  1. href:解析后的 URL。将会作为一个 a 元素的 href attribute。
  2. route:解析后的规范化的地址。
  3. navigate:触发导航的函数。会在必要时主动阻止事件,和 router-link 同理。
  4. isActive:如果须要利用激活的 class 则为 true。容许利用一个任意的 class。
  5. isExactActive:如果须要利用准确激活的 class 则为 true。容许利用一个任意的 class。

扩大

款式 scoped

vue2

/* 深度选择器 */
/*形式一:*/
>>> .foo{ }
/*形式二:*/
/deep/ .foo{ }
/*形式三*/
::v-deep .foo{ }

vue3

/* 深度选择器 */
::v-deep(.foo) {}

.env环境扩大

vite中的.env文件变量名肯定要以VITE_前缀

.env文件

VITE_USE_MOCK = true

应用:

import.meta.env.VITE_APP_CONTEXT

应用Composition API替换mixin

家喻户晓应用mixin的时候当咱们一个组件混入大量不同的mixin的时候,会存在两个非常明显的问题:命名抵触和数据起源不清晰。

  • 每个mixin都能够定义本人的propsdata,它们之间是无感的,所以很容易定义雷同的变量,导致命名抵触。
  • 另外对组件而言,如果模板中应用不在以后组件中定义的变量,那么就会不太容易晓得这些变量在哪里定义的,这就是数据起源不清晰。

以这个经典的Vue 2组件为例,它定义了一个”计数器”性能:

//counter.js
export default {
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count++;
    }
  }
}

用法如下:

<template>
  <div>
    {{ count }}
    <el-button @click="increment()">add</el-button>
  </div>
</template>

<script>
import counter from './mixins/counter'
import getTime from './mixins/getTime'

export default {
  mixins: [counter,getTime]
}
</script>

假如这边咱们援用了counter和getTime两个mixin,则无奈确认count和increment()办法起源,并且两个mixin中可能会呈现反复命名的概率

上面是应用Composition API定义的完全相同的组件:

// counter.ts
import { ref } from 'vue';

export default function () {
    const count = ref(0);
    function increment() {
        count.value++;
    }
    return { count, increment };
}
<template>
  <div>
    {{ count }}
    <el-button @click="increment()">add</el-button>
  </div>
</template>

<script lang="ts">
  import { defineComponent } from 'vue';
  import counter from '/@/composables/counter';

  export default defineComponent({
    setup() {
      const { count, increment } = counter();
      return {
        count,
        increment,
      };
    },
  });
</script>

总结

应用Composition API能够清晰的看到数据起源,即便去编写更多的hook函数,也不会呈现命名抵触的问题。????

Composition API 除了在逻辑复用方面有劣势,也会有更好的类型反对,因为它们都是一些函数,在调用函数时,天然所有的类型就被推导进去了,不像 Options API 所有的货色应用 this。另外,Composition API 对 tree-shaking 敌对,代码也更容易压缩。vue3的Composition API会将某个逻辑关注点相干的代码全都放在一个函数里,这样当须要批改一个性能时,就不再须要在文件中跳来跳去

参考文章 ????

❤️ 疾速应用Vue3最新的15个罕用API

❤️ vue 3.x 如何有惊无险地疾速入门

扩大 ????

如果你感觉本文对你有帮忙,能够查看我的其余文章❤️:

???? 10个简略的技巧让你的 vue.js 代码更优雅????

???? 零距离接触websocket????

???? Web开发应理解的5种设计模式

???? Web开发应该晓得的数据结构

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理