vue3官方文档——互动教程学习笔记
Notes
2023-01-13 12006字

Vue.js guide

文本插值

<span>Message: </span>

双大括号标签会被替换为相应组件实例中 msg 属性的值。同时每次 msg 属性更改时它也会同步更新。可以理解为将数据替换为对应的值(以纯文本形式)。

Attribute 绑定

<div v-bind:id="dynamicId"></div>

类似:key="user.id"将key属性和user.id属性绑定,一变则变。

指令 Directives

<p v-if="seen">Now you see me</p>

这里,v-if 指令会基于表达式 seen 的值的真假来移除/插入该 <p> 元素。

声明响应式状态

我们可以使用 reactive() 函数创建一个响应式对象或数组:

import { reactive } from 'vue'

const state = reactive({ count: 0 })

要在组件模板中使用响应式状态,需要在 setup() 函数中定义并返回。

import { reactive } from 'vue'

export default {
  // `setup` 是一个专门用于组合式 API 的特殊钩子函数
  setup() {
    const state = reactive({ count: 0 })

    // 暴露 state 到模板,让模板可以使用
    return {
      state
    }
  }
}

ref() 定义响应式变量

reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref

import { ref } from 'vue'

const count = ref(0)

ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象:

const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

上手Vue

Vue是js框架,基于HTML、CSS、JS构建。

下面是一个最基本的示例:

import { createApp } from 'vue'

createApp({
    data() {
        return {
            count: 0
        }
    }
}).mount('#app')
<div id="app">
    <button @click="count++">
        Count is: 8
    </button>
</div>

单文件组件

Vue 的单文件组件会将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里,即为.vue文件。下面是简单的计数器组件:

<script>
export default {
    data() {
        return {
            count: 0
        }
    }
}
</script>

<template>
	<button @click="count++">
        Count is:	8
    </button>
</template>

<style scoped>
    button {
        font-weight: bold;
    }
</style>

API风格 选项式更加反人类,但是阅读方便,组合式更好写,阅读难顶。

创建一个运用

确保安装了Node.js,运行npm init vue@latest,这一指令将会安装并执行create-vue,相当与安装并创建一个vue应用。运行完一套命令之后效果:

image

JS教程

JS和java没有任何关系,其原来名字叫做LiveScript,只是后来为了蹭Java热点而更名为JavaScript。

JS的类型有(实际上一切皆对象):

对象

将Object理解为“名称-值”对,可以类比C++中的哈希表。

变量

let 语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。

const 允许声明一个不可变的常量。这个常量在定义域内总是可见的。

var 是最常见的声明变量的关键字。它没有其他两个关键字的种种限制。

控制结构

独特的循环结构:for ... offor ... in

函数

function add() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i < j; i ++) {
        sum += arguments[i];
    }
    return sum;
}

实际上,arguments代表传入参数数组。

Vue3互动教程

文档地址:[教程 Vue.js](https://cn.vuejs.org/tutorial/#step-1)

vue3版hello world

<!-- App组件 -->
<template>
<div id="app">
  <h1></h1>
  <p>Count is: </p>
</div>
</template>

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

export default {
  setup() {
    const counter = reactive({ count: 0 })
    const message = ref('Hello World!')

    return {
      counter,
      message
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

main.js文件如下:

// main.js文件
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app') // 相当于创建一个App应用,以App组件格式创建,挂载id为app的div

效果如图:

image

这个原理非常有意思,首先reactive和ref都是Vue提供的api,其中前者只适用于对象,后者则适用于所有,但是需要使用.value属性暴露值。

属性绑定

使用v-bind:来进行属性绑定,值得注意的是,可以缩写为:

<!-- App.vue文件 -->
<template>
  <h1 :class="titleClass">Make me red</h1>
</template>


<script>
import { ref } from 'vue';

export default {
  setup() {
    const titleClass = ref("title");

    return {
      titleClass,

    }
  }
}
</script>


<style>
.title {
  color: red;
  text-align: center;
}
</style>

这里首先定义了一个titleClass变量,赋值为”title”,同时将titleClass变量暴露给模板,模板使用:进行类绑定,绑定变量titleClass,并且实际上,真正渲染时,使用的是变量存储的值。

事件监听——计数器实现

v-on绑定事件,常常缩写为@

<!-- App.vue文件 -->
<template>
  <div style="text-align:center">
    <button @click="increment" type="button" class="btn btn-primary">count is: 8</button>
  </div>
</template>
<script>
import { ref } from 'vue';
export default {
  setup() {
    const count = ref(0);

    const increment = () => {
      count.value++;
    };

    return {
      count,
      increment,
    };
  }
}
</script>

<style>

</style>

表单绑定

对于输入来说,比较常用的功能是将输入框中的值和某个值绑定起来,这种称之为双向绑定,使用v-model实现。

image

<!-- App.vue文件 -->
<template>
  <input v-model="text" placeholder="请在这里输入:">
  <p>text is: </p>
</template>
<script>
import { ref } from 'vue';
export default {
  setup() {
    const text = ref("");

    return {
      text,

    }
  }
}
</script>

<style>

</style>

条件渲染

image-20230118200050918

列表渲染

<!-- App.vue文件 -->
<template>
<div id="app">
  <form >
    <input v-model="newTodo">
    <button>Add Todo</button>
  </form>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
</div>
</template>



<script>
import { ref } from 'vue';
export default {
  setup() {
    let id = 0; // 给每个对象一个id

    const newTodo = ref('');
    const todos = ref([
      {id: id++, text: "Learn HTML"},
      {id: id++, text: "Learn JavaScript"},
      {id: id++, text: "Learn Vue"},
    ]);

    const addTodo = () => {
      todos.value.push({id: id++, text: newTodo.value});
      newTodo.value = "";
    };

    const removeTodo = (todo) => {
      todos.value = todos.value.filter((t) => t !== todo);
    };
    return {
      id,
      todos,
      newTodo,
      addTodo,
      removeTodo,
    }
  }
}
</script>

<style>

</style>

信息传递

详见项目。

Vue3项目实现

Vue3概念太多,但是好理解,React则是最后抽象的概念少,但是难理解。

配置环境

关于Ubuntu环境下载最新版本Nodejs,可以参考ubuntu快速安装最新版nodejs。记得更换完源之后中间插入一步sudo apt-get update

vue会自带一个图形化界面,输入vue ui即可。

根据教程创建项目,创建完的效果图如下:

image

安装插件router,设置网站路由;vuex,允许我们在多个组件间维护同一个数据,官方状态管理库。

运行依赖需要安装bootstrap,允许我们做很多美工的工作。

任务中进行调试,serve是调试环境,build将所有文件打包运行,因此,当点击serve中的启动后,点击输出,出现如下界面,则表示应用创建成功。

image

一般我们会使用vscode打开项目文件夹,在vs中进行代码编写。

image

技巧:

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/> // 引入HelloWorld组件,msg是指传入信息
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'HomeView',
  components: {
    HelloWorld
  }
}
</script>

整体框架实现

架构图

页面可以粗暴分为navbar和content两部分。以该项目为例,可以切割为如下页面:

image

删除各种无关组件

删除HelloWorld组件和AboutView组件,顺便删除router/index.js中相关路由。删除后整体如下:

image

其中,router/index.js删去无关路由后代码如下:

import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  // {
  //   path: '/about',
  //   name: 'about',
  //   // route level code-splitting
  //   // this generates a separate chunk (about.[hash].js) for this route
  //   // which is lazy-loaded when the route is visited.
  //   component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  // }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router

每个vue组件最后都会导出一个默认对象,第一个属性为name,第二个为components,表示组件在template区域中会用到哪些其它组件,将用到的组件全部放到components中。

创建navbar组件

<template>:内容模板元素是一种用于保存客户端内容机制,解析器在加载页面时确实会处理 <template> 元素的内容,但这样做只是为了确保这些内容有效;但元素内容不会被渲染。例如以下例子:

<table id="producttable">
  <thead>
    <tr>
      <td>UPC_Code</td>
      <td>Product_Name</td>
    </tr>
  </thead>
  <tbody>
    <!-- 现有数据可以可选地包括在这里 -->
  </tbody>
</table>

<template id="productrow">
  <tr>
    <td class="record"></td>
    <td></td>
  </tr>
</template>

首先,我们有一个表,稍后我们将使用 JavaScript 代码在其中插入内容。然后是模板,它描述了表示单个表行的 HTML 片段的结构。既然已经创建了表并定义了模板,我们使用 JavaScript 将行插入到表中,每一行都是以模板为基础构建的。

同理,定义组件时,先定义html模板,然后定义js部分,最后是css部分。

记得需要添加如下运行依赖:

image

通过bootstrap来得到导航栏组件,完成后整体效果如下:

image

创建首页

效果如下:

image

创建好友列表及其它页面

由于发现导航栏和下面的container格子区域实际上是每个页面都统一的,因此考虑将它们统一起来。然后将所有页面更新到路由中,路由代码如下:

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import UserListView from '../views/UserListView'
import UserProfileView from '../views/UserProfileView'
import LoginView from '../views/LoginView'
import RegisterView from '../views/RegisterView'
import NotFoundView from '../views/NotFoundView'


const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  // {
  //   path: '/about',
  //   name: 'about',
  //   // route level code-splitting
  //   // this generates a separate chunk (about.[hash].js) for this route
  //   // which is lazy-loaded when the route is visited.
  //   component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  // }
  {
    path: '/userlist',
    name: 'userlist',
    component: UserListView
  },
  {
    path: '/userprofile',
    name: 'userprofile',
    component: UserProfileView
  },
  {
    path: '/login',
    name: 'login',
    component: LoginView
  },  
  {
    path: '/register',
    name: 'register',
    component: RegisterView
  },  
  {
    path: '/404',
    name: '404',
    component: NotFoundView
  },
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

实现前端渲染

如果直接修改NavBar中的路径,将<a class="nav-link active" href="#">好友列表</a>改为<a class="nav-link active" href="/userlist",通过观察网络,

image

发下每次变更页面,都会重新刷新一边,即向服务器发送请求,使用的是后端渲染。

使用vue框架提供的跳转页面方式,即可实现前端渲染,NavBar.vue代码如下:

<template>
<!-- 引入bootstrap导航栏样式 -->
<nav class="navbar navbar-expand-lg bg-body-tertiary">
  <div class="container">
    <router-link class="navbar-brand" :to="{name: 'home'}">Myspace</router-link>
    <!-- 意思是将这个页面调到home对应的路由页面里面 -->
    <!-- router-link 本质上也是a标签,只不过里面有特殊属性,vue中给某个标签绑定属性用': + 名称' -->
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarText">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <router-link class="nav-link active" :to="{name: 'home'}">首页</router-link>
        </li>
        <li class="nav-item">
          <router-link class="nav-link" :to="{name: 'userlist'}">好友列表</router-link>
        </li>
        <li class="nav-item">
          <router-link class="nav-link" :to="{name: 'userprofile'}">好友动态</router-link>
        </li>
      </ul>
      <ul class="navbar-nav">
        <li class="nav-item">
          <router-link class="nav-link active" :to="{name: 'login'}">登录</router-link>
        </li>
        <li class="nav-item">
          <router-link class="nav-link" :to="{name: 'register'}">注册</router-link>
        </li>
      </ul>
      
    </div>
  </div>
</nav>

</template>


<script>
// 将这个组件导出
export default {
    name: "NavBar",
}
</script>



<style scoped> /* scoped保证组件间css选择器不会相互影响 */


</style>

实现好友动态

整体架构如下:

image

因此考虑使用三个组件来实现。

同时,我们也需要考虑vue在不同组件之间传递信息,父组件给子组件通过props属性来传递信息,子组件给父组件通过触发事件方式来传递信息。

实现效果如图(其中关注按钮可以添加减少粉丝数): image

image

实现帖文列表 image

以发帖按钮为例,当我们点击发帖时,由于在子组件中定义了<button @click="post_a_post" type="button" class="btn btn-primary btn-sm">发帖</button>,就会触发子组件的post_a_post函数,而子组件的该函数会使用context.emit('post_a_post')来触发父组件post_a_post事件,父组件的该事件会调用父组件的post_a_post函数,该函数会更新posts对象,posts对象是reactive类型变量,当reactive类型变量变化时,会自动修改所有引用这个变量的组件。

image

好友列表

通过get方法访问api,这样就可以从云端获得信息,api是由提供者后端实现(例如使用Django框架的某些包),实际上,前端和后端连接就是靠api。

登录页面

image

这里登录需要将用户名和密码存储到后端数据库,传统的模式是Client将用户名 + 密码发送给Server,Server会返回一个session_id,同时将sesson_id存储在数据库中。当Client访问请求时,都必须在请求数据中加入session_id,服务器通过session_id是否存在来判断用户是否注册过。

传统的方式将sesson_id存储在cookie中,而js是无法访问cookie的,使得跨域访问变得麻烦。JWT(json web token)则更方便实现了这点。Client将用户名+密码发送给Server,Server则返回一个jwt(字符串形式),并且不会存储jwt。后续Client再次发送请求时,将发送信息加入在jwt中发送,Server验证jwt是否合法(通过公钥私钥验证)。

使用GET方法时,所有的数据都放到链接中,虽然使用了https协议,但是本质上也并不安全。POST方法是将数据存到http包中,会更加安全。

base64编码是将任意字符转化为不含特殊字符的字符串。

注册页面

image-20230118123154410

后面与数据库交互部分通过调用api完成,过程不好描述。

项目代码已存储至github仓库