最近回顾前端,先是 React 现在因工作需又要开始 vue 了。开发过程中多半时间我们需要结构,有层次有上下级,有秩序。有了秩序也就是约束,有了约束也就是有了规则。从而减少我们思考时间。因为麦当劳只是在 10 点半前提供早餐,我们就无需考虑在 10 点半后买到经济实惠的早餐。
但有时候我们还需要打破秩序,逆流而上。做 web 开发有一段时间,最近遇到复杂业务还是有点摸不到头脑,再加上时间紧任务重往往写出繁琐难懂 code。一直在想如何能够轻松地开发出流畅 web。
其实 web 应用中,数据流是从服务端流出在流回到服务端,知道程序启动了这个数据就开始流动。但是有的人会问,那么从服务端获取列表数据展示,数据也没有流回到服务端,这样可以想象为原封不动地流回到服务端。希望我们多一点想象。
我们应用是界面是一颗 UI 树,每一个节点可能是原生 DOM 或是自定义 component ,component 也是 DOM 组成。树接口是非线性的,但是也是有层次的。我们控制数据在这棵树上流动。数据让我们应用之树变得绚丽多彩,数据是 UI 树的血液,数据让 UI 树有了生命力。
我们将树进行划分区域来控制数据,我们控制数据流动范围,控制数据流动方向。
数据多数流三种方式,框架都应该给出这种三种流动方式给出答案
- 从父级向子级传递数据
- 从子级向父级传递数据
- 跨越多重级别传递数据
从父组件向子组件传递数据(props)
<template>
<div>
<div class="text-4xl text-white bg-blue-200">{{title}}</div>
</div>
</template>
- 在子组件中定义 props 可以接受从父级传过来变量
- 父组件
<template>
<div >
<Title class="text-lg" title="user list"/>
<div class="" v-for="user in users" :key="user.id">
<User message="hello" v-bind:user="user"/>
</div>
</div>
</template>
- 父级调用子组件(User) 可以通过属性 message 传递字符串 hello 给子组件,这种方式我们只能传递字符串。如果传递 data 中一个对象 user 这种方式是无法接受到 user 而是接受到 ”user“ 的字符串。这时候我们就需要用到动态绑定。
动态绑定
- 下面示例是一个在父级获取用户列表然后将列表中 User 对象传递给 User 组件显示出来用户信息。
父组件
<template>
<div >
<div class="" v-for="user in users" :key="user.id">
<User message="hello" v-bind:user="user"/>
</div>
</div>
</template>
<script>
import axios from "axios";
import User from "../components/User.vue";
export default {
name: "Users",
components: {
User
},
data: () => {
return {
users: []
};
},
created() {
axios.get("https://jsonplaceholder.typicode.com/users").then(response => {
console.log(response.data);
this.users = response.data;
});
}
};
</script>
<style>
</style>
- v-bind:user 这种方式可以将 user 对象传递给子组件 User
子组件
<template>
<div>
<p>{{user.name}}</p>
<p>{{user.email}}</p>
</div>
</template>
<script>
export default {
name: "User",
props: ["user", "message"],
data: () => {
return {};
}
};
</script>
<style>
</style>
我们可以为 prop 定义类型,定义了类型也就是约束少了一些选择,选择少了我们就更明确我们能够得到什么 string 类型必填的 title 的属性。
export default {
name: "Title",
props: {
title: {
type: String,
required: true
}
}
};
从子组件向父组件传递数据(事件)
父组件
这个实例是这样的,一个课程列表,列表中每个课程都有一个等级,通过 rating 属性来表示为 1 - 5 。我们选择了一个课程,这是现实该课程的等级,等级信息是从父级组件 Tuts
传递给 Rating 子组件,不过在子组件如果修改了 Rating 反过来也会修改父级组件中的该对象的 Rating 的值,也就是(从子组件向父组件传递数据)
<template>
<div class="flex flex-row justify-center align-center">
<div class="flex flex-col">
<div class="mr-12" v-for="tut in tuts" :key="tut.id" @click="setSelectedTut(tut)">
<div >{{tut.name}}({{tut.rating}})</div>
</div>
</div>
<Rating v-if="selectedTut" :selectedTutRating="selectedTut.rating" v-on:ratingUpdated="changeDisplayRating"/>
</div>
</template>
<script>
const tutList = [
{ id: 0, name: "angularjs", rating: 3 },
{ id: 1, name: "react", rating: 1 },
{ id: 2, name: "vue", rating: 5 }
];
import Rating from "../components/Rating.vue";
export default {
name: "tuts",
data() {
return {
tuts: [],
selectedTut: null
};
},
mounted() {
this.tuts = this.getTuts();
},
methods: {
setSelectedTut(tut) {
this.selectedTut = tut;
},
changeDisplayRating(val) {
this.tuts[this.selectedTut.id].rating = val;
},
getTuts() {
return [...tutList];
}
},
components: {
Rating
}
};
</script>
<style>
</style>
- 先来看父组件如何向子组件传递数据,数据中有两个数据 tuts 课程列表和 selectedTut 表示选中的课程,在 mouted 阶段获取 tuts 数据,如果没有选择任何课程,selectedTut 默认为空,通过 v-if 控制 Rating 子组件显示,没有选择也就意味不会显示 Rating。如果有选择,这回通过 :selectedTutRating 将课程的等级数据传递给子组件 Rating 进行显示。
- 在 setSelectedTut 这个方法在选择课程后会把选择课程设置为 selectedTut。
子组件代码
<template>
<div class="inline-block md:inline-flex">
<form action="">
<div><input type="radio" value="1" id="oneStarts" v-model="tutRating"><label for="oneStarts">1</label></div>
<div><input type="radio" value="2" id="twoStarts" v-model="tutRating"><label for="twoStarts">2</label></div>
<div><input type="radio" value="3" id="threeStarts" v-model="tutRating"><label for="threeStarts">3</label></div>
<div><input type="radio" value="4" id="fourStarts" v-model="tutRating"><label for="fourStarts">4</label></div>
<div><input type="radio" value="5" id="fiveStarts" v-model="tutRating"><label for="fiveStarts">5</label></div>
</form>
</div>
</template>
<script>
export default {
data() {
return {
tutRating: null
};
},
props: {
selectedTutRating: {
type: Number,
required: false
}
},
mounted() {
this.tutRating = this.selectedTutRating;
},
methods: {
clearRating() {
this.tutRating = null;
}
},
watch: {
tutRating: function(newRating, oldRating) {
let success = true;
if (success) {
this.$emit("ratingUpdated", newRating);
}
},
selectedTutRating: function(newRating, oldRating) {
this.tutRating = newRating;
}
}
};
</script>
<style>
</style>
- 在子组件中关键是用了 watch 监控 tutRating 和 selectTutRating 进行控制,当我们在 Rating 进行选择就是通过 tutRating 方法中 $emit 发送给用户新选择的等级给父组件的
v-on:ratingUpdated="changeDisplayRating" 父组件是通过监听ratingUpdated 来从子组件中获取等级数据来实现从子组件到父组件传递数据的。