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 基于标准 HTML 拓展了一套模板语法,将js的状态和HTML进行连接。
- 响应性:Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM。
单文件组件
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应用。运行完一套命令之后效果:
JS教程
JS和java没有任何关系,其原来名字叫做LiveScript,只是后来为了蹭Java热点而更名为JavaScript。
JS的类型有(实际上一切皆对象):
Number
(数字),全部都是浮点数,即console.log(3 / 2);
,输出为1.5。String
(字符串),本质也是对象,因此可以使用"hello".length;
。Boolean
(布尔)Symbol
(符号)Object
(对象)null
(空)undefined
(未定义)
对象
将Object理解为“名称-值”对,可以类比C++中的哈希表。
变量
let
语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。
const
允许声明一个不可变的常量。这个常量在定义域内总是可见的。
var
是最常见的声明变量的关键字。它没有其他两个关键字的种种限制。
控制结构
独特的循环结构:for ... of
和for ... 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
效果如图:
这个原理非常有意思,首先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
实现。
<!-- 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>
条件渲染
列表渲染
<!-- 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则是最后抽象的概念少,但是难理解。
配置环境
- 安装Nodejs,一定要注意需要更新到最新版,至少也要跟上LTS版本号,使用
sudo npm install -g n
和sudo n stable
来升级到LTS版本。
关于Ubuntu环境下载最新版本Nodejs,可以参考ubuntu快速安装最新版nodejs。记得更换完源之后中间插入一步sudo apt-get update
- 使用
npm i -g @vue/cli
,安装vue的脚手架。
vue会自带一个图形化界面,输入vue ui
即可。
根据教程创建项目,创建完的效果图如下:
安装插件router
,设置网站路由;vuex
,允许我们在多个组件间维护同一个数据,官方状态管理库。
运行依赖需要安装bootstrap
,允许我们做很多美工的工作。
任务中进行调试,serve
是调试环境,build
将所有文件打包运行,因此,当点击serve
中的启动后,点击输出,出现如下界面,则表示应用创建成功。
一般我们会使用vscode打开项目文件夹,在vs中进行代码编写。
技巧:
-
将router/index.js的createWebHashHistory改为createWebHistory,网址路径就不会包含
#
。 -
createApp(App).use(store).use(router).mount('#app')
,创建一个名为App的根组件,将router直接引用到代码中(参考js语法),将store(即vuex)也自动引用进来;将整个App挂载到id为#app
的元素上。 -
<!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="/favicon.ico"> <title>myspace</title> <script defer src="/js/chunk-vendors.js"></script><script defer src="/js/app.js"></script></head> <body> <noscript> <strong>We're sorry but myspace doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> /* 只有一个div,将所有的js文件打包成一个js文件,然后挂载到这个div中 */ <!-- built files will be auto injected --> </body> </html>
后端渲染模式:每次打开一个新的页面,就向服务器中发送一个请求,服务器返回该页面的样式文件。
前端渲染模式:只有当第一次打开这个网站的某个页面时,向服务器中发送一个请求,服务器返回整个网站的样式文件,当请求该网站的其它页面时,就不用再重新发送请求了。
-
每个页面都会定义一个
.vue
文件,每个.vue
都由HTML部分、JS部分、CSS部分组成。一反之前将三个部分代码分开放的开发思想,vue将三者直接放在了一个页面中。CSS有个属性scoped
,能够让不同组件之间的css不被相互影响,本质上是让每一个css选择器,当被打包到前端时,加上一个随机值,这样就不会相互影响,这样我们使用vue开发时,就不用考虑组件之间选择器的相互影响。 - 当我们实现完一个组件后(例如组件
HelloWorld
),页面可以通过
<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>
@
是v-on
,用于绑定事件,:
是v-bind
,用于绑定属性和变量。
整体框架实现
架构图
页面可以粗暴分为navbar和content两部分。以该项目为例,可以切割为如下页面:
删除各种无关组件
删除HelloWorld
组件和AboutView
组件,顺便删除router/index.js中相关路由。删除后整体如下:
其中,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部分。
记得需要添加如下运行依赖:
通过bootstrap来得到导航栏组件,完成后整体效果如下:
创建首页
效果如下:
创建好友列表及其它页面
由于发现导航栏和下面的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"
,通过观察网络,
发下每次变更页面,都会重新刷新一边,即向服务器发送请求,使用的是后端渲染。
使用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>
实现好友动态
整体架构如下:
因此考虑使用三个组件来实现。
同时,我们也需要考虑vue在不同组件之间传递信息,父组件给子组件通过props属性来传递信息,子组件给父组件通过触发事件方式来传递信息。
实现效果如图(其中关注按钮可以添加减少粉丝数):
实现帖文列表
以发帖按钮为例,当我们点击发帖时,由于在子组件中定义了<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类型变量变化时,会自动修改所有引用这个变量的组件。
好友列表
通过get
方法访问api,这样就可以从云端获得信息,api是由提供者后端实现(例如使用Django框架的某些包),实际上,前端和后端连接就是靠api。
登录页面
这里登录需要将用户名和密码存储到后端数据库,传统的模式是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编码是将任意字符转化为不含特殊字符的字符串。
注册页面
后面与数据库交互部分通过调用api完成,过程不好描述。
项目代码已存储至github仓库。