1、创建Message组件
前面我们获取到了请求错误的信息,所以我们接下来做一个弹出框组件,让错误提示展示出来
我们把这个组件做成一个全局组件,它不仅可以显示错误的信息,还可以添加成功操作的信息,甚至还可以显示一个普通的提示,所以它应该有三种类型,而且它应该和Loader组件即加载组件一样,一看我们就直到是一个全局性质的组件,所以它应该使用Teleport即瞬间移动添加到根节点去,而不是附属于某一个特定的组件中。同时它右边有一个可关闭的按钮,所以这个组件中应该有一个状态存在,从而控制是否展示,至于样式我们看boostrap
如下,实现把样式div这些搞就来,然后这个组件中我们应该接收两个东西,一个是这个组件的颜色,一个是展示的文字,所以我们如下创建一个MessageType,然后props中接收两个东西message和type,并且我们创建这个message节点
在App.vue中把该组件显示出来,如下
然后我们调整一下样式
接下来我们优化一下,我们发现node创建的过程和取消的过程是一个非常重复的逻辑,我们创建一个单独的useDOMCreate.ts把它抽离出来,如下
全局组件是一种比较熟悉的组件啦,最后我们还自定义了一个钩子函数,抽象出了一段DOM创建和卸载的逻辑,但是现在我们发现我们组件有局限性,就是不知道该怎样快速的展示成功的提示,这里我们是错的提示,难道我们还需要添加一个全局的状态才能展示成功的提示,这当然不是一个好的做法,有没有啥好办法呢,接下来我们来实现
2、Message组件改进为函数调用形式
现在我们组件都是嵌套在组件树中展示的,但是message组件使用这种方式会感觉比较奇怪,它应该像一个函数一样进行工作,如createMessage('hello','error'),就像alert一样,调用一个函数那么弹出提示,点击关闭或者过一会就自动消失,那么我们使用它显示错误或成功的信息就非常简单,这也是组件组织的另外一种常用方式,现在我们来完成这样的改造
那么使用函数调用,那么首先我们要有一个函数,如下我们创建一个函数叫createMessage.ts
难点是怎样用函数的形式来创建一个组件,因为我们之前都是在组件template里面实例化一个组件,这时候我们要记住一个方法,来看到main.ts中是怎么创建呢?如下通过createApp()方法实现,createApp()是直接从vue直接导过来的,它可以生成一个组件的实例
看文档,它有两个参数,第一个参数就是这个组件本身,这个组件其实就是一个Object;第二个参数是props,它组件接收的props
我们来使用一下,如下props接收的就是Message.vue组件中props接收的东西
接下来我们要把messageInstance挂载到一个节点上,我们看看该怎么做,我们在main.ts中也有对应的处理方法,如下,我们使用mount()加上一个DOM节点'#app'即mount('#app')即可把它挂载到dom节点上,接下来就可以显示到界面上
所以我们新建一个节点,让这个创建的组件实例挂载到一个节点上,这个组件就可以在界面上显示出来,并且给个定时任务,到一定时间把这个组件卸载并且把创建的节点也删除掉
现在我们来调用它,如下在App.vue中通过watch去监测error中status的变化,一旦变化就去获取当前error的status、message,如果是有错误并且有message那么就调用这个创建Message组件的方法,即如下
此时我们尝试输错用户名密码,发现会弹出错误提示,并且2s后提示就消失了,但是发现再点击提交的时候发现没有错误提示弹出,这是因为我们第一次弹出后error是true,没有把error的状态恢复,那么error的status就一直是true,它没有变化那么watch就不会触发后面的调用createMessage的方法,所以就不会再展示出报错提示了
所以我们需要在发起请求的时候通过拦截器把error重置一下,重置为false和空如下
然后我们在登录成功以后,给用户提示一下。所以到login.vue中登录成功后就调用它,然后传入的message和type为如下即可展示出如下成功的提示
通过这里,我们要学会使用createApp() 来创建组件实例和mount() 方法挂载展示的一种新的途径,要特别注意学会这个新的知识点
3、了解Vnode 以及 vue 的简单工作原理
我们上面用createApp() 返回的是一个App类型是一个应用实例,之前我们只在main.ts中使用过它,createApp() 出现在main.ts中它是整个应用的应用实例,那我们这里使用这个createApp() 创建一个小小的message组件是不是优点大材小用了,有没有更轻量级的方式来做?这就是接下来要做的,我们先来了解一下vnode的知识,面试中也经常问到这些问题
VitrulalDOM是一种虚拟的,保存在内存中的数据结构,用来代表UI的表现,会和真实的DOM节点保持同步。VitrulalDOM是由一系列的Vnode组成的。
如下,vnode就是一个Object平平无奇,一个type什么类型,props有什么属性,children可能有更多的vnode节点
怎样将一个vnode转换成一个真正的DOM节点呢,我们看看对应的步骤,要牢记这个过程,这个是非常重要的。
第一步我们称为Compile,就是vue组件的Template模块里的代码会被编译成render function,render function 是一个特殊的function,这个函数它可以返回Virtual DOM树(就是返回一系列对应的节点);即绿->黄->紫
第二步称为Mount,也就是vue内部会执行这个render function,遍历这个虚拟的DOM树,生成真正的DOM节点;即紫->蓝
最重要的即第三步即更新的内容,因为我们在组件中有很多的响应式对象,那这些对象发生变化的时候都会发生更新,其实它的过程就来自于这个Patch,当组件中任何响应式对象发生变化的时候就会执行更新操作,就会生成新的虚拟节点树,vue内部会遍历新的虚拟节点树,和旧的树做对比,找出最小阿达更新范围,然后执行必要的更新;即黄->红
虚拟DOM的优点:
· 可以使用一种更方便的方式,供开发者操作UI的状态和结构,不必和真实的DOM节点打交道
(假如我们直接操作真实的DOM,会有一系列DOM提供的API,并且它的速度也是比较慢的)
· 更新效率更高,计算需要的最小化操作,并完成更新
4、创建Vnode 以及使用 render function
创建Vnode:
h 和 createVnode 都可以创建vnode。h 是hyperscript的缩写,很多virtualDOM都使用这个函数名称,还有一个函数称为createVnode。还有一个函数就是这个createVnode,两个函数的用法几乎是一样的。
如下示例,h函数有3个参数,第一个是type这里是div类型的节点,第二个是props是Object形式的属性,第三个是一个数组,里面有一系列的children即子节点。只有两个参数时,就把那个props的参数忽略。创建出来的Vnode中有一系列的属性,但是它的实质就是一个普通的Object,了解这个就可以了
声明 Render Function
当使用composition API 的时候,在setup当中直接返回一个对象,代表着给模板使用的数据,当要使用render function的时候,可以直接返回一个函数。
如下,比如一个ref叫message,直接return这个message,代表着数据message是给模板使用的,当我们要使用render function的时候,可以直接返回一个函数,即如下直接return ()=>{},然后在function当中你就可以返回一系列的vnode节点了
举个例子:
如下,我们不使用template,我们使用render function ,我们在setup中,在return ()=>{}即在return 后的这个函数当中,我们就可以返回一系列的节点了,如下在函数中return h()创建的节点,如下类型是h1,children是props.msg+count.value
然后我们到App.vue中展示出来,如下
那么我们使用render function的形式就成功渲染了一个对应的组件,没有用template。当然它还支持多个节点,就用数组的形式,如下
那么这个时候假如我们只有script即没有template没有那些html了,那么就不需要.vue这个文件形式也可以啦,不需要component的过程,我们直接用ts即可,如下然后把名称改为Vnode.ts即可
可以看到还是会输出对应的不变的这个页面
假如我们一直使用h这样的写法,那么简单的时候还可以,当你有非常多非常多的节点的时候,就会比较繁琐,并且难以读懂,那么能不能有一种更容易让人读懂的格式呢,这时候JSX就出现了
使用JSX
JSX 是一种类似XML的语法,对使用过react的话就会比较熟悉,它就是h函数的一种语法糖,它可以将这种类似HTML的语法转换成h函数的写法。
如下这种<div>hello<div>就是JSX,它就非常像HTML,但是它真正的产出是类似上面的h('div')这样的写法,内部会有对应的转换过程。在其中使用变量的时候,我们使用单括号将它包裹起来就可以了,这是JSX的基本语法
// 创建vnode
const vnode = <div>hello</div>
// 使用变量
const vnode = <div id={dynamicId}>hello,{userName}</div>
添加JSX支持:
终端中输入vue add babel 回车即可,安装后会有babel.config.js文件
它会自动把babel的插件给你安装上,通过babel可以进行转换,把JSX转换成对应的h函数
然后我们到刚才的举例中,我们把对应的h写法转换成对应的JSX,如下
我们是不能直接在ts中使用html的,我们需要把后缀名改为tsx,然后就可以使用html如<div>语法了,
如下,使用html标签把vnode结构写进去,然后babel会把这个JSX转成h函数的形式,然后h函数创建vnode,然后通过render function把vnode生成真正的DOM
5、使用h函数改造message组件
如下我们把createApp函数创建的改为h函数创建的
用来的:
如下,通过h函数,创建了vnode了以后,我们需要将它添加到对应的DOM节点上面去,这比较有难度,我们怎样将一个vnode渲染到一个特定的DOM上面,这时候就用到vue内部的函数叫render函数
然后我们调用一下看看,如下,可以通过调用createMessage() 就展示出message组件
然后再把功能丰富一点在,这里我们是定义了一个定时器固定时间把这个message组件卸载,但是在有些情况下,比如我们是想点击关闭按钮后再把这个message组件关闭,所以我们需要改变一下,这时候我们希望它返回一个实例,上面带一个对应的方法,来把这个message清除掉,这样我们就可以手动调用这个实例的这个方法即可清除message,如下
然后我们在APP.vue中如下
所以最后在login组件中调用这个函数展示这个message组件即可
总结:
我们这一系列做的就是,
我们刚开始做了message组件,我们可以直接在某个组件中通过<message></message>即可调用这个message组件,从而展示出这个提示信息框
但是我们觉得它可以像以前用过的alert一样,直接通过一个函数调用就可以展示出这个组件,所以有了如下一步步改进:
刚开始我们创建一个函数叫createMessage(),函数中我们用vue中的createApp()直接创建组件实例,然后通过mount() 把组件实例挂载到节点中,即可实现直接调用这个createMessage()就能实现创建这个message组件并且展示出来的效果。
但是又感觉有点大材小用,所以我们想换成一个轻量级的方法,我们就想到了vnode,我们通过h()函数创建了vnode(h()函数创建就有点麻烦,我们就使用了JSX创建vnode),然后通过render function把vnode变成了一个真正的DOM,创建了真正的DOM节点了以后,我们这里是通过h()函数把vnode创建起来,然后通过render函数把这个vnode添加到我们新建的DOM节点上面去。
6、注册页面的编写
注册页面Signup.vue的编写如下:
我们之前的做法,
是在ValidateInput中定义验证规则的类型并且定义了输入框失去焦点后触发的函数中就是去遍历验证所有规则,如果是哪个规则则做什么验证这样子。之前定义了email类型和required类型,并且在失去焦点函数中写了这两个规则应该做什么验证,如下如果是email类型则应该符合邮箱格式,如果是required类型则应该不能为空;然后我们会在登录页面定义各个输入框的验证规则具体是什么类型以及如果不符合类型应该返回什么提示信息即message这样子
流程就是登录页面定义了各个输入框有什么规则,如下第一个输入框中绑定的rule比如那个emailRules,这个emailRules有两个规则一个是email一个是required,所以validate-input组件接收到了这两个rule,就会如上,因为有规则所以去遍历传过来的rule,所以进入switch-case判断,如果规则为required则要符合不能为空,如果规则为email则要符合是邮箱格式
我们这个注册页面和登录页面差不多也是一个个input框,有点不一样的就是有一个重复密码的输入框,这个输入框验证规则不仅仅要不能为空,并且还需要添加它输入框内输入的密码和上面密码框输入的密码要一致,所以要在ValidateInput组件中加入这个验证规则
就是要添加一个的这个规则,它要判断它的密码和上面密码框输入的密码是否一致,那么我们就需要这个规则是一个函数,然后函数中做这个判断,而且我们希望它返回一个布尔值,即如果一致则返回true即让我们直到验证通过,否则验证失败这样子
所以我们要回到ValidateInput组件中添加这个规则,即如果某个输入框传过来的rule中有这个规则,那么我们在遍历规则们的switch-case中添加,如下,如果规则名是custom则如下做判断
然后注册组件中,给这个重复密码的这个输入框加入这个规则,这个规则中的validator函数中做我们要做的逻辑判断
注意,在validateinput组件中只是定义这个validator函数,并且在switch-case遍历到它的时候去调用执行它,但是这个函数里面具体有什么逻辑是我们在登录组件中定义这个规则的时候定义的逻辑,为什么是在登录页面写定具体逻辑而不是在validateInput组件中呢,你在validateInput中写定了的话你以后无论是哪个组件用这个规则,那么这个规则就定死了即只能是判断重复密码这个输入框的信息是否等于密码输入框的信息,但是如果你在登录页面写定逻辑而validateInput中只是调用,那么你想在登录页面的这个custom规则中执行判断重复密码这个输入框的信息是否等于密码输入框的信息可以,想在其他页面custom规则中执行判断重复密码这个输入框的信息是否等于邮箱输入框的信息也可以,即可以随意自定义你想要进行的逻辑,就不会定死
然后添加这个注册的路由,并且到GlobalHeader组件中把点击注册按钮就跳转到注册页面
我们到接口文档中看,当你创建一个新的用户,那么服务端会创建一个新的用户并且返回如下数据,返回的数据中有这个用户的_id、email、nickName,还有个column id,这是我们在创建一个用户的时候它会自动为这个用户创建一个它对应的column,用户和column应该是一个一对一的关系。如下,用户注册时应该发送post请求,让服务端新建用户,这个post请求只是把当前输入的用户信息发送给服务端即可,服务端验证成功后会返回新建这个用户的用户信息
由于注册过程对全局store没有做任何改变,所以没有新建什么action,因为这个请求成功以后只是跳转到登录页面去让用户登录,所以我们这里直接使用axios.post发送请求即可