前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实用的VUE系列——快速使用 vue ,就要鄙视他,理解他,成为他

实用的VUE系列——快速使用 vue ,就要鄙视他,理解他,成为他

作者头像
用户7413032
发布2024-05-07 09:46:25
650
发布2024-05-07 09:46:25
举报
文章被收录于专栏:佛曰不可说丶佛曰不可说丶

声明:本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

刚接触vue 我们应该学什么?

最近,发现掘金 vue3的教程逐渐增多,让我感慨颇深,偶然间想起一句 至理名言

鄙视**vue** 理解**vue** 成为**vue**

vue3之所以能成为,三大框架之一,到底是为什么?

我记得我在之前的文章中说过,因为好用

可是,虽然好用,也得分什么人用,怎么用

不信?

我举几个例子

1、你有没有在某个深夜,用reactive 拉取了一个接口的然后赋值之后,发现视图并没有任何变化,然后加班加点排查

代码语言:javascript
复制
const obj = reactive({});
requst().then((res) => {
  obj = res;
});

2、你有没有好奇过,为什么 我在模板语法中,不需要用.value 而在js 的代码中,有需要加上 .value

代码语言:javascript
复制
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
  count.value++
}
</script>
<template>
  <button @click="increment">
    {{ count }} // 不需要调用.value
  </button>
</template>

3、你有没有疑问,别人都说vue3vue2好用,到底好在哪? 不就是api、生命周期变化? 怎么我用着感觉一样呢, 甚至还不如2

4、你有没有在某一天学了es6解构赋值,突然想在vue3里头用一次, 发现vue不让,你必须这样,你有没有想过为啥吗?

代码语言:javascript
复制
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>{{ description }}</p>
  </div>
</template>
 
<script>
import { ref } from 'vue';
 
export default {
  props: {
    title: String,
    description: String
  },
  setup(props) {
    // 解构赋值使用props中的数据,必须要用toRefs
    const { title, description } = toRefs(props);
 
    return {
      title,
      description
    };
  }
};
</script>

5、你有没有思考过,nextTickupdated那个先执行?

所以,想要用好vue 你光看文档是不行的 就要鄙视他,理解他,成为他

说人话,就是你要去死记硬背文档的内容,不是不行,而是万万的不行,文档就静静的躺在那里,你可以随时去翻。

而我们要做的,就是深刻的理解,vue 底层的一些设计思路,从而推导出 ,上层的表现

也就是所谓的授人以鱼不如授人以渔

所以,我们打通任督二脉的关键点就是,要理解哪些设计思路,这才是关键。

骥某人不才,vue3也写过两年,源码也看过一点, 略懂,略懂,

就献丑帮大家筛选下,我们到底要理解哪些原理/思想 ,其实说白了就是想通一些事!

响应式原理

说起响应式原理,其实就是一个老生常谈的问题,而且我之前也写过文章,文章在这,无非就是一个Proxy 简单的使用方式如下

代码语言:javascript
复制
// 使用Proxy实现完美劫持
const obj = { name: "vue", arr: [1, 2, 3] };
function proxyData(value) {
  const proxy = new Proxy(value, {
    get(target, key) {
      console.log(`get key is ${key}`);
      const val = target[key];
      if (typeof val === "object") {
        return proxyData(val);
      }
      return val;
    },
    set(target, key, value) {
      console.log(`set key is ${key}, value is ${value}`);
      return (target[key] = value);
    },
    deleteProperty(target, key) {
      console.log(`delete key is ${key}`);
    },
  });
  return proxy;
}
const proxy = proxyData(obj);
proxy.age = 18; // 可对新增属性进行劫持
delete proxy.name; // 可对删除属性进行劫持
proxy.arr.push(4); // 可对数组的push等方法进行劫持
proxy.arr[3] = 4; // 可对象数组的索引操作进行劫持

代码写完了,

然而我要说的是,这玩意除了面试的时候能在面试官面前显摆一下,屁用没有,因为你依然不了解,模板是怎么 跟响应式 有关系的,为啥响应式的值改变了,会影响模重新渲染,这才是你理解整个vue3 的关键

说到这

我们就可以回答第一个问题用**reactive** 拉取了一个接口的然后赋值之后,发现视图并没有任何变化

其实原因也很简单,我们看到的并不一定是我们看到的

很多人思考问题,容易陷入一种思维惯性,比如

代码语言:javascript
复制
let obj = reactive({});

在上述代码中,我们按照之前的思维逻辑思考问题,很容易陷入一种惯性,obj 就是响应式,我不论如何对obj 做更改,他都应该是响应式的

然而事实是, obj 并不是响应式,他存的地址对应的 对象才是响应式的,他存的仅仅是响应式的地址而已,引用类型 这是我们入行的时候必看的课题之一

所以,当我们对于obj 做更改

代码语言:javascript
复制
 obj ={a:1}

其实此时地址被更换了,响应式的对象自然就没了, 而obj 就不再是响应式的了,那么请问,你还怎么能更新模板呢?

神奇不,真实的情况就这么绕!!

而我们正确的做法,不让这个响应式地址丢失

代码语言:javascript
复制
 obj = Object.assign(obj,{a:1})

理解了这个问题,我们就能理解es6解构赋值 为什么会破坏响应式,其实本质上异曲同工

好,接下来,我们开始正题

为啥模板能跟响应式页面联动

说起这个问题,我们要先从模板的历史讲起。。。。

讲起模板语法的历史,其实其中蕴含着的互联网技术发展历史,如果我要从源头讲起,我相信,我会讲的很累,大家也会听的很累。

所以我决定,用一句话解决问题

所谓模板语法,本质就是在刀耕火种的互联网年代,想要**html** 的数据能够变成动态的而产生的一种规范,仅此而以

既然是规范,那么就可以借鉴,于是vue 毫不犹豫,拿了过来

拿是拿过来了, 为什么要拿过来呢?vue借鉴项目github如下[github.com/fb55/htmlpa…]

我相信,很多人都没有想过这个问题,我想过

其实原因很简单,为了能快速上手

相信用过react的人都知道,想要直接在浏览器引用一个react 的包就能跑起来,是相当费劲的 ,想要不学习就能凭借之前的经验和直觉,就能写出代码,是没指望的 ,想要不用脚手架,就能写个简单项目,是不可能的

不信?

请看代码:

代码语言:javascript
复制
  <div id="root"></div>
    <script src="https://cdn.bootcdn.net/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>
    // 竟然还要用babel
    <script src="https://unpkg.com/@babel/standalone@7.18.4/babel.min.js"></script>
    <script type="text/babel">
        class App extends React.Component {
            constructor() {
                super()
                this.state = {
                    num: 1
                }
            }
            add = () => {
                this.setState({
                    num: this.state.num + 1
                })
            }
            render() {
                return <button onClick={this.add}  >
                    {this.state.num}
                </button>
            }
        }
        window.onload = function () {
            ReactDOM.render(<App />, document.querySelector('#root'))
        }
    </script>

接下来, 我们再来反观vue 就上手的难度来看,高下立判!

代码语言:javascript
复制
  <div id="app">{{ message }}</div>
    <script type="module">
        import { createApp, ref } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'

        createApp({
            setup() {
                const message = ref('Hello Vue!')
                return {
                    message
                }
            }
        }).mount('#app')
    </script>

这就是模板语法的优势,直接在html中修改变量,更符合传统的编码直觉

接下来第二个问题来了,数据怎么跟模板实现联动的呢?

先上官网图,其实,之所以模板能够和数据联动,究其原因很简单, 模板被转换了,变成了一个函数,因为只有这样我们才能在数据变动的时候去执行函数、更新界面,从而实现联动

有了这个前提,我们不需要去看vue 源码就能很轻松的理解,

其实很简单,每一个响应式的值,保存着跟他相关的render函数,仅此而已,当值被更新的时候,顺带的跟他相关的render函数,也就是模板被重新执行,实现页面更新

理解了这个原理,我们就能从代码中打断点来解决业务中遇到的问题,因为浏览器中就是编译后的render 函数

打断点

我们在vite 项目中,利用浏览器来对,render函数打断点

如图:

我们只需要在需要拿到值的地方断点即可

这就是我们理解了所谓的模板,就是render函数之后的魔力

当然,你如果觉得render 断点之后,想要看到结果是非常麻烦的,我们依然可以利用render函数的特性,既然他是个函数,那么当然可以console.log

于是

代码语言:javascript
复制
<template>
  <div>假设我们要看到数据并且打印出来,看看模板中的问题{{ log(loginMsg) }}</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const loginMsg = ref('');

const log = (loginMsg) => {
  console.log(loginMsg)
};

于是我们就能清晰的看到模板中的每一个值,并且从而更容易的排查问题!!

理解了render函数的真面目,我们就能回答在文章开头疑惑的问题,为什么我们在模板中不需要使用.value呢?

其实原因很简单,render 函数是编译来的,既然是编译,那就能包装,于是,就诞生了一个取值的逻辑,来处理模板中的.value

如图,有一个toDisplayString方法

代码如下:

代码语言:javascript
复制
export const toDisplayString = (val: unknown): string => {
  // 判断了值类型
  return isString(val)
    ? val
    : val == null
      ? ''
      : isArray(val) ||
          (isObject(val) &&
            (val.toString === objectToString || !isFunction(val.toString)))
        ? // 保证他是一个字符串 第二个参数是为了过滤内容,最终拿到相对应的value的值
          JSON.stringify(val, replacer, 2)
        : String(val)
}

const replacer = (_key: string, val: any): any => {
  if (val && val.__v_isRef) {
    // 这个地方处理了.value
    return replacer(_key, val.value)
  } else if (isMap(val)) {
    return {
      [`Map(${val.size})`]: [...val.entries()].reduce(
        (entries, [key, val], i) => {
          entries[stringifySymbol(key, i) + ' =>'] = val
          return entries
        },
        {} as Record<string, any>,
      ),
    }
  } else if (isSet(val)) {
    return {
      [`Set(${val.size})`]: [...val.values()].map(v => stringifySymbol(v)),
    }
  } else if (isSymbol(val)) {
    return stringifySymbol(val)
  } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) {
    // native elements
    return String(val)
  }
  return val
}

vue3为什么要hooks

hooksvue3出来以前,是react的特权,很多人只知道,要用hooks 却不知道,用他的目的,

其实他的目的很简单,代码逻辑复用

vue2,其实一直没有解决这个问题,所以vue3 迫切的需要解决这个问题 于是vue版本的hooks 横空出世 当然,他还有一个更高端的名字,composition api

composition api

在开始讲 composition api 之前,我们先温习一下,vue2 是怎么实现逻辑复用的

vue2 逻辑服用,之前使用的是Mixin

Mixin

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

代码如下:

代码语言:javascript
复制
   // 混入内容
  export const mixins = {
    data() {
      return {
        msg: "这是值",
      };
    },
    computed: {},
    created() {
      console.log("我是mixin中的created生命周期函数");
    },
    mounted() {
      console.log("我是mixin中的mounted生命周期函数");
    },
    methods: {
      clickMe() {
        console.log("我是mixin中的点击事件");
      },
    },
  };
  // 使用混入
  
 // src/App.vue
<template>
<div id="app">
  <img alt="Vue logo" src="./assets/logo.png" />
  <button @click="clickMe">点击我</button>
</div>
</template>

<script>
import { mixins } from "./mixin/index";
export default {
name: "App",
mixins: [mixins],
components: {},
created(){
  console.log("组件调用minxi数据",this.msg);
},
mounted(){
  console.log("我是组件的mounted生命周期函数")
}
};
</script> 

不知各位jym看到上述代码,有没有什么想法?

反正我有

  • 1、它命名冲突了咋办?
  • 2、他后期混入了很多个组件,怎么维护
  • 3、他怎么找到源头呢? 我都不知道这个方法哪里来的?
  • 4、我多个混入到一个组件,冲突咋办?

所以,发现了吗,Mixin 他不好用,出现的问题比解决的问题还多,于是 composition api 横空出世

composition api

他是为了解决什么问题呢?

解决,**react** 有,而**vue**没有的问题!!

额!!!开玩笑!

他本质是为了解决mixin 的这些缺点,当然,也是为了看齐react hooks 的能力,毕竟,实现逻辑复用有很多种,他偏偏选了这种 自己去细品

在开始之前我们先了解一下composition api 怎样使用呢?

其实所谓composition api 就是一些响应式方法,比如refcomputedrefwatchreactive等。。。 例如

代码语言:javascript
复制
  import { ref, defineComponent, computed } from "vue";

  export default defineComponent({
    setup() {
    //composition api ref
      const count = ref(0);
  
      const color = ref("red");
      const changeColor = () =>
        (color.value = color.value === "red" ? "green" : "red");
  
      return () => (
        <div>
          <div style={{ color: color.value }}>{count.value}</div>
          <button onClick={changeColor}>改变颜色</button>
        </div>
      );
    },
  });

那么composition api 怎么实现逻辑复用呢?

很简单,用一个方法封装,将composition api 导出来,不就可以了吗?

如此一来 我们每次在setup 中初始化当前方法,就相当于初始化一套新的composition api响应式结果

完美的解决了Mixin 的所有问题

例如:

代码语言:javascript
复制
// 封装axios 请求实现逻辑复用
// 每次请求
import { ref } from "vue";
import axios from "axios";

export default function useFetch(url) {
  const data = ref(null);
  const error = ref(null);
  const isLoading = ref(true);

  axios.get(url)
    .then(response => {
      data.value = response.data;
    })
    .catch(errorResponse => {
      error.value = 'Error: ' + errorResponse.response.status + ' - ' + errorResponse.response.statusText;
    })
    .finally(() => {
      isLoading.value = false;
    });

  return {
    data,
    error,
    isLoading
    
    
  };
}

从上述代码中我们可以看出useFetch 可以到处复用,并且不会产生污染,他只会在当前setup独一份的生效!!

这就是composition api 逻辑复用的魅力!!

composition api的组合式 与传统声明式的对比

vue3的使用方式,俗称组合式 就是利用 composition api 的特点,在setup 中任意自由组合,来解决vue2中规矩太多的问题,也就是达到react 灵活的特点

我们从一张图中可以看出

如上图所示

composition api由于任意自由组合 的特点,我们可以将相同的逻辑可以写在同一个地方,这些逻辑甚至可以使用函数抽离出来,各个逻辑之间界限分明,方便维护

当然,这有一个前提,他是需要修炼的

而反观声明式的Options API 由于限制和规矩,导致你需要严格的按照规则书写,这反倒成了新手的天堂,毕竟想象力,和归纳能力,不是初出茅庐就能有的。

他需要大量的训练和总结

到了这,我们就能顺利的解释之前的第三个问题都说**vue3**比**vue2**好用,到底好在哪?

答案其实很简单,抛开性能的提升,vue3最大的提升 俩字自由

从此,你就可以不再受限制,展翅高飞,随意的提炼设计你的代码,自由这俩字其实就是程序员编程的乐趣,不是吗?

这也是很多技术大佬喜欢react的原因,因为他实在太自由了。

翱翔!!!!

vue 中的 dom 更新时机

接下来,我们来探讨最后一个问题,我们发现几乎所有的框架都有生命周期,这是为什么呢?

要理解这个问题,我们就需要追本溯源,从框架为什么叫做框架说起

所谓框架,其实就是一套开发规范,我们按照规范写代码,框架将规范的代码转换执行组成我们的界面,如此而已

其实我们也可以,自己用原生写,只是他会很浪费时间,因为没有规范,代码就会很凌乱,所谓没有规矩,不成方圆,就是如此

所谓,vuereactanglar 都是如此

这么多年来,无数的人,比来比去,骂来骂去,本质上,就是比较那种规范更好用,仅此而已!!

而要实现这种规范,框架就需要给我们做大量的工作,来承接规范到可执行代码的中间地带,而生命周期,就是这种承接所必备的模块

原因很简单,既然是规范就不能随心所欲,但又要让用户能完成功能,所以在每个时期就得能让用户操作自己的代码

vue 中总的来说有那么几个生命周期

1、beforeCreate: 组件实例刚被创建,组件属性计算之前。

2、created: 组件实例刚被创建,属性已绑定,但 Dom 还未生成。

3、beforeMount: 模板编译/挂载之前。在挂载开始之前被调用,相关的 render 函数首次被调用,实例已完成以下的配置: 编译模板,把 data 里面的数据和模板生成 html,此时注意还没有挂载到 Html 页面上。

4、mounted: 模板编译/挂载之后。在 el 被新创建的 vm.el 替换,并挂载到实例上去之后被调用,实例已完成以下的配置:用上面编译好的的 html 内容替换 l 属性指向的 Dom 对象。完成模板中的 html 渲染到 html 页面中。此过程进行 ajax 交互。

5、beforeUpdate: 组件更新之前。在数据更新之前调用,发生在虚拟 Dom 重新渲染打补丁之前,可以在钩子函数中进一步的更改状态,不会出大附加的重渲染过程。

6、updated: 组件更新之后。在由于数据更改导致的虚拟 Dom 重新渲染和打补丁之后调用。调用是,组件 Dom 已经更新,所以可以执行依赖于 Dom 的操作,然而在大多数的情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环,该钩子函数在服务器端渲染期间不被调用。

7、beforeDestroy: 组件销毁前调用。在示例销毁之前调用,实例仍然完全可用。

8、destroyed: 组件销毁后调用。 在实例销毁之后调用。调用后,所有的时间监听会被移除,所有的子实例也会被销毁,该钩子函数在服务器端渲染器件不被调用

好了,片汤话讲完了,这些都是文档上的话,翻翻看看即可。

接下来,讲点正题, 还是之前的那个问题 nextTick**和** updated**那个先执行?**

这其实是一个非常有意思的问题,因为都是 dom 更新之后调用,那么顺序到底是什么呢?

我们可以用以下代码来佐证

代码语言:javascript
复制
<template>
  <div >
    <div @click="b">{{ a }}</div>
  </div>
</template>
<script setup lang="ts">
import { ref, onUpdated, nextTick } from "vue";
let a = ref(1);
nextTick(() => {
  console.log(444444444);
});
onUpdated(() => {
  console.log(22222222);
});
nextTick(() => {
  console.log(333333333);
});
const b = () => {
  a.value++;
  nextTick(() => {
    console.log(1111);
  });
};
</script>

// 444444444
// 333333333
// 点击之后
// 22222222
// 1111

我们知道,onUpdatednextTick 最后都会推入微任务队列 中,只是前后顺序的问题。

从以上代码可以看出,onUpdated 方法 在初始化之后,应该是没有被推入微任务队列, 而是保存在了某个地方,

不然,初始化之后,应该是打印三个值,而现今只有两个,

而当我点击之后发现, onUpdated 先打印, nextTick 后打印,则可说明, 在点击的时候, onUpdated的函数 先被推入微任务队列, nextTick 随后

这样,我们就能不通过,查看源码来合理的分析出,整个vue 底层生命周期的运行轨迹,并且熟练运用在自己的业务中

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-05-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 刚接触vue 我们应该学什么?
  • 响应式原理
    • 为啥模板能跟响应式页面联动
      • 打断点
      • vue3为什么要hooks
        • composition api
          • Mixin
        • 反正我有
          • composition api
            • composition api的组合式 与传统声明式的对比
            • vue 中的 dom 更新时机
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
            http://www.vxiaotou.com