Vue学习笔记流(上)
Vue学习笔记流(上)
1. 创建项目
-
xxx安装@vue/cli(npm i -g @vue/cli)
-
vue,启动!-- 使用vue ui命令
- 在 输出的命令行内,我们可以进行访问前端界面了
2. vue项目前置基本概念
java html js css基础略。
大佬们常说的三剑客框架对应vue文件里的三个标签 (分别对应 html & js & CSS),
<template>
</template>
<script>
</script>
<style scoped>
</style>
vue重要优势是通过使用scoped,不同CSS样式之间互不影响,提供了组件化框架,原理是使用了随机值区分、
2.1 vue重要组件属性速查
script部分
export default
对象的属性:
name
:组件的名称components
:存储<template>
中用到的所有组件props
:存储父组件传递给子组件的数据watch()
:当某个数据发生变化时触发computed
:动态计算某个数据setup(props, context)
:初始化变量、函数ref
定义变量,可以用.value
属性重新赋值reactive
定义对象,不可重新赋值props
存储父组件传递过来的数据context.emit()
:触发父组件绑定的函数
template部分
<slot></slot>
:存放父组件传过来的children
。v-on:click或@click
属性:绑定事件v-if、v-else、v-else-if
属性:判断v-for
属性:循环,:key
循环的每个元素需要有唯一的keyv-bind:或:
:绑定属性
style部分
<style>
标签添加scope
属性后,不同组件间的css不会相互影响。
第三方组件
view-router包
:实现路由功能。vuex
:存储全局状态,全局唯一。state
: 存储所有数据,可以用modules
属性划分成若干模块getters
:根据state
中的值计算新的值mutations
:所有对state
的修改操作都需要定义在这里,不支持异步,可以通过$store.commit()
触发actions
:定义对state的复杂修改操作,支持异步,可以通过$store.dispatch()
触发。注意不能直接修改state
,只能通过mutations
修改state
。modules
:定义state
的子模块
3. 第一个重要组件(NavBar)
实现结果,这也是大部分厂家喜欢的样式,牛客网,力扣都是如此
3.1 借助bootstrap构建样式
构思框架
NavBar组件vue脚本(bootstrap模板拿过来套就完事)
在本阶段我们修改的地方大致是:
- 修改具体的值
- 我们增添了重定向,即:to属性
<template>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container">
<router-link class="navbar-brand" :to="{name: 'home', params: {}}">My Space</router-link>
<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', params: {}}">首页</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'userlist', params: {}}">好友列表</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'userprofile', params: {}}">用户动态</router-link>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<router-link class="nav-link active" :to="{name: 'login', params: {}}">登录</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'register', params: {}}">注册</router-link>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script>
export default {
name: "NavBar",
}
</script>
<style scoped>
</style>
3.2 挂载bootstrap依赖
- 别忘了在挂载点App 引入我们的bootstrap组件和依赖
4. 创建基本视图view
对应的代码
实现效果
4.1 ContentBase(slot)
比如,每个界面我都有一个card 作为ContentBase(名字要组合名字)
- 很重要的一个点:使用slot属性存放父组件传过来的children
- 引申:react框架使用的是this.children
<template>
<div class = "home">
<div class="container">
<div class="card">
<div class="card-body">
<slot></slot>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "ContentBase",
}
</script>
<style scoped>
.container {
margin-top: 20px;
}
</style>
4.2 HomeView
我在HomeView中就可以在js里面声明,引用这个组件来写了
<template>
<ContentBase>
首页
</ContentBase>
</template>
<script>
import ContentBase from "../components/ContentBase";
export default {
name: 'HomeView',
components: {
ContentBase,
}
}
</script>
<style scoped>
</style>
4.3 UserListView
<template>
<contentBase>
好友列表
</contentBase>
</template>
<script>
import ContentBase from "../components/ContentBase"
export default {
name: 'UserListView',
components: {
ContentBase,
}
}
</script>
<style scoped>
.container {
margin-top: 20px;
}
</style>
其他三个界面同理,名字改一下就是可以得到一个初步的界面了。
5. 主页面路由跳转实现(:to)
- 实现跳转第一种方法,但是每次都要访问服务器,这不是前端渲染。
- 使用【:to】属性,里面有name和params
- 小技巧:使用alt添加光标,同步修改
- 发现每次请求其他界面的时候没有请求,那么这就实现前端渲染了。
至此,我们就可以通过导航栏实现路由跳转了
6. 用户动态基础页面
6.1 UserProfileInfo-Grid system
最终效果
- 使用到了bootstrap grid system来实现一个布局
- 十二等分,我这里用了39开
-
关注一下实际开发中的快捷写法(我的理解的话 '>'是孩子 '+'是兄弟)
div.row>(div.col-3+div.col-9)
6.1 UserProfileInfo-.img-fluid
图片部分用到了自适应图片,需要使用到.img-fluid类
用到的按钮组件在这
这个按钮我们改一下padding和字体大小
然后我们把这个grid用卡片存到一起
<template>
<div class="card">
<div class="body">
<div class="row">
<div class="col-3">
<img width="200" class="img-fluid" src="https://cdn.acwing.com/media/user/profile/photo/141472_lg_e98a1ce65b.jpg" alt="">
</div>
<div class="col-9">
<div class="username">{{ fullName }}</div>
<div class="fans">粉丝数:{{ user.followerCount }}</div>
<button @click="follow" v-if="!user.is_followed" type="button" class="btn btn-secondary btn-sm">+关注</button>
<button @click="unfollow" v-if="user.is_followed" type="button" class="btn btn-success btn-sm">取消关注</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'UserProfileInfo',
}
}
</script>
<style scoped>
img {
border-radius: 50%;
}
.username {
font-weight: bold;
}
.fans {
font-size: 12px;
color: grey;
}
button {
padding: 2px 4px;
font-size: 12px;
}
</style>
UserProfileView中导入
<template>
<ContentBase>
<div class="row">
<div class="col-3">
<UserProfileInfo />
<user-profile-write />
</div>
<div class="col-9">
<UserProfilePost :posts="posts"/>
</div>
</div>
</ContentBase>
</template>
<script>
import ContentBase from "../components/ContentBase";
import UserProfileInfo from "../components/UserProfileInfo";
import UserProfilePost from "../components/UserProfilePost";
import UserProfileWrite from "../components/UserProfileWrite";
export default {
name: 'UserList',
components: {
ContentBase,
UserProfileInfo,
UserProfilePost,
UserProfileWrite,
},
}
</script>
<style scoped>
</style>
至此,基本的界面就搭建好了。
*7. 个人信息子模块(父子组件信息传递)
7.1 父组件和子组件通信原理
跟react原理相似的
- 父组件是通过
props
方式来与子组件通信的(见信息绑定)- 在父组件中
UserProfileView
中具体实现setup()
初始化变量函数,变量的赋值用到了reactive
,注意return
- 注意:在
setup()
中定义的变量,需要逐一return
(key: value
如果一样 可以只写一个) - 赋值两种方式 借助
ref
或者是reactive
对象,其中,ref
比reactive
运行慢- 但ref的使用场景是可以重新赋值(
.value
)而reactive不行。
- 但ref的使用场景是可以重新赋值(
- 在父组件中
- 子组件是通过
event
方式来与父组件通信的(见关注,取关功能)- 具体的实现可以通过click来触发我们所定义的函数,从而通过
emit
来向父组件提交事件 - 注:事件的名称可以任意指定
- 具体的实现可以通过click来触发我们所定义的函数,从而通过
7.2 父组件和子组件的变量绑定
:user
是v-bind:user
的缩写,从而实现子组件和父组件该变量的绑定- 子组件使用props接收
7.3 重要功能--关注&取关
- 首先我们要明确一点,这是我们通过子组件的
button
按钮来实现绑定参数user
的改变,最后通过button
返回的,子组件向父组件传递参数要通过事件为媒介。 - 要实现关注
event
,我们是需要用子组件的关注按钮来触发事件从而调用父组件的函数,这里用到了button
的@click="follow" @click="unfollow"
来触发向父组件提交事件的函数context.emit('follow')
后面可以接参数(发帖功能)注意return
- 然后当然要在父组件定义函数
follow
和unfollow
,注意也要返回return
,然后我需要定义@follow="follow"
从而声明follow事件触发后,我要调用follow函数 unfollow同理- follow函数实现逻辑是如果is_followed为false 那么就改为true,并且user.followcount ++,unfollow反过来
- 最后在button里面使用
v-if
来判断user的值,返回按钮的结果。
7.4 代码实现
请看最后的代码
7.4.1 父组件UserProfileView
<template>
<ContentBase>
<div class="row">
<div class="col-3">
<UserProfileInfo @follow="follow" @unfollow="unfollow" :user="user" />
<user-profile-write @post_a_post="post_a_post" />
</div>
<div class="col-9">
<UserProfilePost :posts="posts"/>
</div>
</div>
</ContentBase>
</template>
<script>
import ContentBase from "../components/ContentBase";
import UserProfileInfo from "../components/UserProfileInfo";
import UserProfilePost from "../components/UserProfilePost";
import UserProfileWrite from "../components/UserProfileWrite";
import { reactive } from 'vue';
export default {
name: 'UserList',
components: {
ContentBase,
UserProfileInfo,
UserProfilePost,
UserProfileWrite,
},
setup() {
const user = reactive({
id: 1,
username: "lingjunyi",
lastName: "Ling",
firstName: "JunYi",
followerCount: 0,
is_followed: false,
});
const follow = () => {
if (user.is_followed) return ;
user.is_followed = true;
user.followerCount ++ ;
};
const unfollow = () => {
if (!user.is_followed) return ;
user.is_followed = false;
user.followerCount -- ;
}
return {
user,
follow,
unfollow,
}
}
}
</script>
<style scoped>
</style>
7.4.2 子组件UserProfileInfo
- 这里用到了
computed
实现动态计算 - 子组件接收绑定
v-bind
的user属性的话,这里需要一个props
定义在里面 - 这样就实现了动态计算(即改变user的值 可以动态改变这个值 因为我把user在父组件和子组件上绑定了)
<template>
<div class="card">
<div class="body">
<div class="row">
<div class="col-3">
<img width="200" class="img-fluid" src="https://cdn.acwing.com/media/user/profile/photo/141472_lg_e98a1ce65b.jpg" alt="">
</div>
<div class="col-9">
<div class="username">{{ fullName }}</div>
<div class="fans">粉丝数:{{ user.followerCount }}</div>
<button @click="follow" v-if="!user.is_followed" type="button" class="btn btn-secondary btn-sm">+关注</button>
<button @click="unfollow" v-if="user.is_followed" type="button" class="btn btn-success btn-sm">取消关注</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { computed } from 'vue';
export default {
name: 'UserProfileInfo',
props: {
user: {
type: Object,
required: true,
},
},
setup(props, context) {
let fullName = computed(() => props.user.lastName + ' ' + props.user.firstName);
const follow = () => {
context.emit('follow');
}
const unfollow = () => {
context.emit('unfollow');
}
return {
fullName,
follow,
unfollow,
}
}
}
</script>
<style scoped>
img {
border-radius: 50%;
}
.username {
font-weight: bold;
}
.fans {
font-size: 12px;
color: grey;
}
button {
padding: 2px 4px;
font-size: 12px;
}
</style>
至此,我们在原有布局的基础上 实现了第一个组件--个人信息UserProfileInfo子组件(带关注功能)
8. 帖子列表子模块(v-for)
8.1 父组件UserProfile
我们在父组件UserProfile
加入以下内容,本质上是通过数组实现的。
<template>
<ContentBase>
<div class="row">
<div class="col-3">
<UserProfileInfo @follow="follow" @unfollow="unfollow" :user="user" />
<user-profile-write @post_a_post="post_a_post" />
</div>
<div class="col-9">
<UserProfilePost :posts="posts"/>
</div>
</div>
</ContentBase>
</template>
<script>
import UserProfilePost from "../components/UserProfilePost";
import UserProfileWrite from "../components/UserProfileWrite";
export default {
name: 'UserList',
components: {
ContentBase,
UserProfileInfo,
UserProfilePost,
UserProfileWrite,
},
setup() {
const posts = reactive({
count: 3,
posts:[
{
id: 1,
userId: 1,
content: "我今天学web课了,好开心!",
},
{
id: 2,
userId: 1,
content: "今天学算法课了,好开心!!",
},
{
id: 3,
userId: 1,
content: "玩到原神了,好开心!!!",
},
]
})
return {
posts,
}
}
}
</script>
8.2 子组件UserProfilePost
子组件UserProfilePost
实现
- 这里用到了
v-for
,需要用:key
绑定key属性,保证唯一 - 对于posts里的每一个元素(posts对象里的posts数组),我构造一个div,这里我每一个div写一个card
- 不推荐用下标,因为下标可能会有冲突
- 等价写法
<template>
<div class="card">
<div class="card-body">
<div v-for="post in posts.posts" :key="post.id">
<div class="card single-post">
<div class="card-body">
{{post.content}}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "UserProfilepost",
props: {
posts: {
type: Object,
required: true,
},
}
}
</script>
<style scoped>
.single-post {
margin-bottom: 10px;
}
</style>
至此,我们在用户界面实现了帖子的显示功能了。
9. 发帖子模块(v-model)
9.1 父组件UserProfile
将子组件加入父组件UserProfile
<template>
<ContentBase>
<div class="row">
<div class="col-3">
<UserProfileInfo @follow="follow" @unfollow="unfollow" :user="user" />
<user-profile-write @post_a_post="post_a_post" />
</div>
<div class="col-9">
<UserProfilePost :posts="posts"/>
</div>
</div>
</ContentBase>
</template>
<script>
import ContentBase from "../components/ContentBase";
import UserProfilePost from "../components/UserProfilePost";
import UserProfileWrite from "../components/UserProfileWrite";
import { reactive } from 'vue';
export default {
name: 'UserList',
components: {
ContentBase,
UserProfileInfo,
UserProfilePost,
UserProfileWrite,
},
setup() {
const posts = reactive({
count: 3,
posts:[
{
id: 1,
userId: 1,
content: "我今天学web课了,好开心!",
},
{
id: 2,
userId: 1,
content: "今天学算法课了,好开心!!",
},
{
id: 3,
userId: 1,
content: "玩到原神了,好开心!!!",
},
]
})
const post_a_post = (content) => {
posts.count ++ ;
posts.posts.unshift({
id: posts.count,
userId: 1,
content: content,
})
}
return {
posts,
post_a_post,
}
}
}
</script>
9.2 子组件UserProfileWrite
子组件UserProfileWrite
实现
使用到了form control模板
- 如何获取发帖的内容?这里用到了
v-model
让textarea的内容
绑定content变量
- 发帖点击按钮后,触发我们定义一个函数post_a_post,函数的内容就是触发父组件的事件
emit
,并借助ref
清空content
的内容 - 父组件的事件触发后,调用
post_a_post
函数,实现在数组最前面加一个元素array.unshift
,实现发帖。- 注:为什么要用
reative
? 这是为了当变量值发生变化时,会修改所有引用该变量的组件。
- 注:为什么要用
<template>
<div class="card edit-field">
<div class="card-body">
<label for="edit-post" class="form-label">编辑帖子</label>
<textarea v-model="content" class="form-control" id="edit-post" rows="3"></textarea>
<button @click="post_a_post" type="button" class="btn btn-primary btn-sm">发帖</button>
</div>
</div>
</template>
<script>
import { ref } from 'vue' ;
export default {
name: "UserProfileWrite",
setup(props, context) {
let content = ref('');
const post_a_post = () => {
context.emit('post_a_post', content.value);
content.value = "";
}
return {
content,
post_a_post,
}
}
}
</script>
<style scoped>
.edit-field {
margin-top: 20px;
}
button {
margin-top: 10px;
}
</style>
至此,我们在用户界面实现了帖子的发送功能了。