Vue学习笔记流(上)

Vue学习笔记流(上)

1. 创建项目

  • 安装Node.js

  • xxx安装@vue/cli(npm i -g @vue/cli)

  • vue,启动!-- 使用vue ui命令

image-20240102094835282

image-20240102094911100

  • 在 输出的命令行内,我们可以进行访问前端界面了

2. vue项目前置基本概念

java html js css基础略。

image-20240102095225413 image-20240102095649509

大佬们常说的三剑客框架对应vue文件里的三个标签 (分别对应 html & js & CSS),

<template>
</template>

<script>
</script>

<style scoped>
</style>

vue重要优势是通过使用scoped,不同CSS样式之间互不影响,提供了组件化框架,原理是使用了随机值区分、

image-20240102095952247

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循环的每个元素需要有唯一的key
  • v-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)

实现结果,这也是大部分厂家喜欢的样式,牛客网,力扣都是如此

image-20240102101454033

3.1 借助bootstrap构建样式

构思框架

image-20240102100852163

NavBar组件vue脚本(bootstrap模板拿过来套就完事)

image-20240102101213955

在本阶段我们修改的地方大致是:

  • 修改具体的值
  • 我们增添了重定向,即: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组件和依赖

image-20240102101333698

4. 创建基本视图view

对应的代码

image-20240102101940545

实现效果

image-20240102101645279

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>

image-20240102102712870

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>

image-20240102103155457

其他三个界面同理,名字改一下就是可以得到一个初步的界面了。

5. 主页面路由跳转实现(:to)

image-20240102103107037
  • 实现跳转第一种方法,但是每次都要访问服务器,这不是前端渲染。
image-20240102103549571 image-20240102103608419
  • 使用【:to】属性,里面有name和params
  • 小技巧:使用alt添加光标,同步修改
  • 发现每次请求其他界面的时候没有请求,那么这就实现前端渲染了。

image-20240102103804427

image-20240102103948258

至此,我们就可以通过导航栏实现路由跳转了

6. 用户动态基础页面

6.1 UserProfileInfo-Grid system

最终效果

image-20240102113145796

  • 使用到了bootstrap grid system来实现一个布局
  • 十二等分,我这里用了39开
image-20240102113653287
  • 关注一下实际开发中的快捷写法(我的理解的话 '>'是孩子 '+'是兄弟)

    div.row>(div.col-3+div.col-9)

image-20240102113926752

6.1 UserProfileInfo-.img-fluid

图片部分用到了自适应图片,需要使用到.img-fluid类

image-20240102115004225

用到的按钮组件在这

image-20240102115150653

image-20240102115832601

这个按钮我们改一下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. 个人信息子模块(父子组件信息传递)

image-20240102153208990

7.1 父组件和子组件通信原理

跟react原理相似的

image-20240102122216382
  • 父组件是通过props方式来与子组件通信的(见信息绑定)
    • 在父组件中 UserProfileView中具体实现setup()初始化变量函数,变量的赋值用到了reactive,注意return
    • 注意:在setup()中定义的变量,需要逐一returnkey: value如果一样 可以只写一个)
    • 赋值两种方式 借助ref或者是reactive对象,其中,refreactive运行慢
      • 但ref的使用场景是可以重新赋值(.value)而reactive不行。
  • 子组件是通过event方式来与父组件通信的(见关注,取关功能)
    • 具体的实现可以通过click来触发我们所定义的函数,从而通过emit来向父组件提交事件
    • 注:事件的名称可以任意指定

7.2 父组件和子组件的变量绑定

  • :userv-bind:user的缩写,从而实现子组件和父组件该变量的绑定
  • 子组件使用props接收

7.3 重要功能--关注&取关

  • 首先我们要明确一点,这是我们通过子组件的button按钮来实现绑定参数user的改变,最后通过button返回的,子组件向父组件传递参数要通过事件为媒介
  • 要实现关注event,我们是需要用子组件的关注按钮来触发事件从而调用父组件的函数,这里用到了button@click="follow" @click="unfollow" 来触发向父组件提交事件的函数context.emit('follow') 后面可以接参数(发帖功能)注意return
  • 然后当然要在父组件定义函数followunfollow,注意也要返回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)

image-20240102153224283

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
  • 不推荐用下标,因为下标可能会有冲突
  • 等价写法
image-20240102154109988
<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)

image-20240102153234131

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-modeltextarea的内容绑定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>

至此,我们在用户界面实现了帖子的发送功能了。

posted @ 2024-01-07 13:47  yuezi2048  阅读(6)  评论(0编辑  收藏  举报