Vue和React对比
Vue和React对比
Vue也已经升级到2.0版本了,到现在为止(2016/11/19)比较流行的MVVM框架有AngularJS(也有人认为其为MVC)、ReactJS和VueJS,这三个框架中,以我现在的情况来说(AngularJS2还没有去接触),ReactJS和VueJS的对比应该是比较适合的,感觉这哥俩就是好基友,不管是单向数据流、组件化思想、还是构建大型应用的路由和状态管理都有些许相似之处。而AngularJS与Jquery对比我个人觉着比较合适。
为什么现在MVVM框架这么火呢?JQuery挺好用的呀,为什么要去代替它?...
可能会产生这样的疑问,在我看来,MVVM框架的流行是因为随着前端技术的发展对于要求越来越高和前端面对的场景越来越复杂导致了现实对于前端性能的要求也越来越高,这样像JQuery那样频繁的操作DOM节点的方式显然就不太合适了。所以MVVM开始逐渐流行开来,另外我认为JQuery目前来看还是不会被代替的,因为对于一些对性能要求不是很高的前端项目,是用JQuery来开发还是非常爽的。
废话有点多了,进入正题。接下来从数据双向绑定、组件及数据流、路由、状态管理等方面来分别对比一下怎样去使用Vue和React。
数据双向绑定
我理解的数据双向绑定是,MVVM框架中的View层和Model层的数据相互影响。那么,那些行为会引起数据变动呢?
首先,View层(即页面上)的表单操作、触发事件可能会引起数据变动;ajax请求也可能会引起数据变动,这个变动我认为更多在Model层;还有一种情况就是,某一数据变动引起另外关联数据的改变。
不管是哪一种数据改变,都会导致View层和Model层的变化,View层变动引起页面变化,Model层变动保存数据。
Vue的数据双向绑定
在Vue中,View层中与数据绑定有关的有插值表达式、指令系统、*Class和Style、事件处理器和表单控件,ajax请求和计算属性也和数据变化有关,下面我们分别说一下这些知识点设计的一些数据绑定问题。
插值表达式
在Vue中,插值表达式和指令对于数据的操作又称为模板语法。
Vue.js 使用了基于 HTML 的模版语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上, Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,在应用状态改变时, Vue 能够智能地计算出重新渲染组件的最小代价并应用到 DOM 操作上。
关于插值表达式的使用,在Vue官网模板语法的插值部分有详细的使用说明,不在赘述,需要注意的是,过滤器在插值中的使用有时可以起到意想不到的效果。
指令
Vue重的指令估计是从Angular那里学来的,有很多相似的地方,但是也不完全相同。
Vue中的指令我觉着非常的简单,并且就12个,很容易记忆:
- v-bind:动态地绑定一个或多个 html属性,或一个组件 prop 到表达式(组件单项数据流使用)。
- v-once:绑定只渲染元素和组件 一次 的特性或prop表达式。
- v-model:在表单控件或者组件上创建双向绑定,代替value值。
- v-text:更新元素的 textContent。
- v-html:更新元素的 innerHTML。注意:内容按普通 HTML 插入 - 不会作为 Vue 模板进行编译 。
- v-on:绑定事件监听器。
- v-if / v-else / v-show:条件渲染。
- v-for:列表渲染。
- v-pre:跳过这个元素和它的子元素的编译过程。
10 v-cloak:这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如[v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
大概列举一下,详细使用请参考Vue API 指令和Vue 指南的Class与Style绑定、条件渲染、列表渲染、事件处理器、表单控件绑定部分内容。
Class与Style绑定
Vue为了方便操作控制元素的样式,专门增强了v-bind:class和v-bind:style,通过增强的这两个指令可以实用对象语法或者数组语法对元素的样式进行变动,这也不是本文重点,Vue官方Class与Style绑定已经说得很详细了。
条件渲染和列表渲染
条件渲染和列表渲染在Vue模板中动态创建模板很不错,让我里面想到了JSP中的EL表达式和Struts中的JSTL(后端模板语言中基本都有),这就可以方便的根据后端传过来的数据进行模板创建了。你懂得,详细语法和使用还是参考Vue文档列表渲染和条件渲染,本篇主题是对比,并不是讲解基础语法,Vue的官方文档我觉着非常给力,简单明了。
事件处理器
在Vue中我们可以通过v-on来给元素注册事件,完成事件注册,Vue中的事件处理和平时使用的事件处理不同之处就是,既可以绑定数据处理函数,也可以使用内联处理器。并且,Vue还讲常用的事件方法,如preventDefault()等通过修饰符的方式来方便使用。
表单控件
你可以用v-model指令在表单控件元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
Vue中对于表单控件提供的v-model*指令非常的使用,可以方便的返回或者设置表单控件的信息。
计算属性
在Vue中引入了计算属性来处理模板中放入太多的逻辑会让模板过重且难以维护的问题,这样不但解决了上面的问题,而且也同时让模板和业务逻辑更好的分离。
Vue计算属性
ajax数据请求
在Vue1.x的版本中,官方推荐的ajax数据请求库是vue-resource,但是在Vue2.0的版本中,不再推荐使用,该推荐使用axios。
其实这些ajax数据请求的使用都大差不差,随你选择,并且vue-resource还是继续支持使用的,在Vue2.0中。
以上八个方面,我个人认为都是和数据绑定相关的一些Vue基本项,只是简单列举,具体内容请查看Vue文档或者API。
为什么这么多呢?因为Vue中有个模板的概念,所以,数据和模板进行数据绑定需要分别来做,而在接下来的React中,你会发现,React的数据绑定虽然也是这八项,但是,不会展开八项来说明。
React中的数据双向绑定
在上面的Vue中,我们已经提及了有八个方面可以影响到数据的改变,但是React中就没有必要展开了说明了,因为在React中没有Vue中模板的概念,因为人家有一个JSX语法呀,可以将HTML和JS还有CSS混合写在一起呀,,这样写成的组件感觉还不错,组件化也是ReactJS的重要特点之一。
React中通过将state(Model层) 与View层数据进行双向绑定达到数据的实时更新变化,具体来说就是在View层直接写JS代码将Model层中的数据拿过来渲染,一旦像表单操作、触发事件、ajax请求等触发数据变化,则进行双向同步。
所以说React的特点是组件化,也就是说,接下来的小节才是React的重点部分。
组件及数据流
前端发展到现在,为了提高开发效率,组件化已经成为一个必然的趋势。而MVVM框架中,如果没有组件化的思想,它都不敢说拿出来宣传(纯属个人意淫,哈哈)。下面我们再分别简单介绍一下VueJS和ReactJS中组件思想和组件之间的数据流。
React中的组件和数据流
上一节中提到过,React中的组件化是其重要的特点之一,因为在Angular1.x的出现,并没有明确提出组件的思想,只是有一个类似的指令思想来实现组件化的方式。所以,当React中明确提出组件思想后,前端好像重生了(吹的有点大了)。
创建组件
React中实现组件有两种方式,一种是createClass方法,另一种是通过ES2015的思想类继承React.Component来实现。
createClass方式实现
import React from 'react';
export default React.createClass({
render() {
return (
<div>hello5</div>
)
}
})
这样,一个组件就创建完成,并且通过ES2015的模块化思想将其暴露出去了,其他组件就可以引入并使用了。如下:
import React from 'react';
import ReactDom from 'react-dom';
import Hello from './hello.jsx';
ReactDom.render(
<Hello/>,
document.getElementById('app');
);
OK,这样就使用简单使用了一个组件。
类继承React.Component来实现
import React from 'react';
class CommentItem extends React.Component {
render() {
return (
<div className="comment">
<span>{ this.props.author }</span>
<span>{ this.props.date }</span>
<div>{ this.props.children }</div>
</div>
)
}
}
export { CommentItem as default };
需要注意的是,这样创建组件的时候,组件名称首字母必须大写(如:CommentItem)。同样,我们使用一下这个组件。
import React from 'react';
import CommentItem from './comment-item';
class CommentList extends React.Component {
render() {
let CommentNodes = this.props.data.map((comment, index) => {
return (
<CommentItem key={index} author={comment.author} date={comment.date}>
{comment.content}
</CommentItem>
)
});
return (
<div>
{ CommentNodes }
</div>
)
}
}
export { CommentList as default };
这样我们又创建了一个组件,并且在这个组件中我们使用了上面创建的那个组件。
组件之间的数据流
在上面类继承React.Component来实现一节中,我们可以看出例子中出现了组件嵌套的情况,仔细想想,组件之间传递信息肯定是必然的。那么React是怎样进行组件之间的数据通信的呢?
回答这个问题之前,我们需要考虑一下,组件之间有几种数据通信。首先,第一种比较容易想到,那就是父子组件之间的数据通信。第二种也就自然而然的出来了----非父子组件之间的数据通信。
父子组件数据通信
父子组件之间的数据通信细分其实还有两种:父与子之间和子与父之间。
在React中,父与子之间的数据通信是通过props属性就行传递的;
而子与父之间的数据通信可以通过父组件定义事件,子组件触发父组件中的事件时,通过实参的形式来改变父组件中的数据来通信;
下面我们来分别通过例子来再现一下这种场景:
父组件:
import React from 'react';
import CommentItem from './comment-item';
class CommentList extends React.Component {
render() {
let CommentNodes = this.props.data.map((comment, index) => {
return (
<CommentItem key={index} author={comment.author} date={comment.date}>
{comment.content}
</CommentItem>
)
});
return (
<div>
{ CommentNodes }
</div>
)
}
}
export { CommentList as default };
子组件:
import React from 'react';
class CommentItem extends React.Component {
render() {
return (
<div className="comment">
<span>{ this.props.author }</span>
<span>{ this.props.date }</span>
<div>{ this.props.children }</div>
</div>
)
}
}
export { CommentItem as default };
通过上面我们可以看出,子组件CommentItem需要父组件传过来的值进行展示,而父组件是这样的:
<CommentItem key={index} author={comment.author} date={comment.date}> {comment.content} </CommentItem>
在父组件中添加了key
、author
、date
属性来向子组件传值。想对象的,子组件通过props对象来获取父组件传过来的值,如下:
<span>{ this.props.author }</span>
<span>{ this.props.date }</span>
<div>{ this.props.children }</div>
好的,我们再来看一下另一种子与父之间的通信。
父组件:
import React from 'react';
import CommentList from './comment-list';
import CommentForm from './comment-form';
class CommentBox extends React.Component {
constructor(props) {
super(props);
this.state = {data: []};
}
handleCommentSubmit(comment) {
let comments = this.state.data;
comments.push(comment);
this.setState({data: comments});
}
render() {
return (
<div className="m-index">
<div>
<h1>评论</h1>
</div>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit.bind(this)} />
</div>
)
}
}
export { CommentBox as default };
子组件:
import React from 'react';
class CommentForm extends React.Component {
handleClick(){
let author = this.refs.author.value,
content = this.refs.content.value;
this.props.onCommentSubmit({author, content, date:new Date().getTime()});
this.refs.author.value = "";
this.refs.content.value = "";
}
render() {
return (
<div className="yo-list yo-list-a">
<label className="item item-input">
<input type="text" className="yo-input flex" ref="author" placeholder="发布人" />
</label>
<label className="item item-input">
<textarea className="yo-input flex" ref="content" placeholder="留言内容"></textarea>
</label>
<label>
<button onClick={this.handleClick.bind(this)} className="yo-btn yo-btn-l">发表评论</button>
</label>
</div>
)
}
}
export { CommentForm as default };
简单解释一下,子组件是一个表单组件,父组件中引用了该表单子组件,然后子组件中点击button按钮,触发子组件中的处理函数,处理函数通过refs获取到表单输入值然后调用父组件中传过来的函数,从而触发父组件中的函数执行改变data数据,data数据变动直接影响的是另一个组件CommentList的变化。
需要注意的是,在获取表单控件内的数据时,我们利用了一个refs对象,该对象用于获取真实DOM结构。具体来说就是,在React中组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM,这是React探索性的创新)。只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff (详细了解diff 算法),它可以极大提高网页的性能表现。
在这里点击button时,input和textarea元素还是虚拟DOM,所以违法获取到输入的值,需要通过refs对象获取一下。
非父子组件之间的通信
React中在处理非父子组件之间的通信时,简单的,嵌套不深的非父子组件(如:兄弟组件)可以仍然使用上一节非父子组件之间通信中的事件函数,传形参的方式来实现。如子组件CommentList 和子组件CommentFrom之间的通信就是这样实现的。
如果,需要通信的两个非父子组件之间嵌套比较深,可以使用Flux和Redux来实现状态管理,这里不做详细阐述,下面会详细对比vue的状态管理进行说明。想先了解的可以看一下阮一峰老师的blog:
Flux 架构入门教程
Redux 入门教程(一):基本用法
Redux 入门教程(二):中间件与异步操作
Redux 入门教程(三):React-Redux 的用法
组件的声明周期
上面这张图已经很清楚的展示了react组件的声明周期了,就不过多介绍了。这张图摘自React组件生命周期小结,对于理解React组件的声明周期钩子函数很有帮助。
Vue中的组件和数据流
Vue比React出来的要晚一些,自然顺应了前端组件化的大潮,并且个人觉得借鉴了部分React的思想来实现其组件化思想。
Vue默认的是单向数据流,这是Vue直接提出来说明的,父组件默认可以向子组件传递数据,但是子组件向父组件传递数据就需要额外设置了。
父子组件之间的数据通信是通过Prop和自定义事件实现的,而非父子组件可以使用订阅/发布模式实现(类似于Angualr中的非父子指令之间的通信),再复杂一点也是建议使用状态管理(vuex)。
我一直觉得Vue的官方文档是我看过最直接、易懂的技术文档,所以就直接给大家贴一个中文链接,自己去跟随尤雨溪学习吧。
状态管理
上面对比React和Vue的组件及数据流的时候,都提到了当非父子组件之间嵌套过深的时候都建议使用状态管理来维护数据的变化,那么到底它们之间的状态管理有什么区别呢?
Vue中的状态管理--vuex
先放个官方中文链接,还是建议直接看官方文档。然后在放一下小例子去体会一下。
先简单说明一下,vuex状态管理的几个核心概念:
- State: Vuex 使用 单一状态树 —— 是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个『唯一数据源(SSOT)』而存在。这也意味着,每个应用将仅仅包含一个 store 实例。
- Getters: 从state中获取状态值
- Mutation: 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutations 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数
- Action: 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。
例子来了:
store.js
import Vue from '../libs/vue.js';
import Vuex from '../libs/vuex.min.js';
Vue.use(Vuex);
const state = {
loginPrePath:['/']
};
const mutations ={
LOGINPREPATH(state,path){
state.loginPrePath.unshift(path);
},
LOGINPREPATHSHIFT(state){
state.loginPrePath.shift();
}
};
export default new Vuex.Store({
state,
mutations
});
actions.js:
export default {
loginPrePath: ({dispatch,state},path)=>{
console.log('actions loginPrePath:' +path);
dispatch('LOGINPREPATH',path);
},
loginPrePathShift: ({dispatch,state})=>{
console.log('delete....');
dispatch('LOGINPREPATHSHIFT');
}
}
getter.js:
export default {
loginPrePath: state => state.loginPrePath
};
login.vue:
<template>
...
</template>
<script>
import Vue from '../libs/vue.js';
import VueResource from '../libs/vue-resource.js';
import Vuex from '../vuex/actions.js';
import VuexGet from '../vuex/getters.js';
Vue.use(VueResource);
export default {
data(){
return {
username: '',
password: '',
loginBtn: 0
}
},
vuex: {
actions: {
setLoginPrePath: Vuex.loginPrePath
},
getters:{
getLoginPrePath: VuexGet.loginPrePath
}
},
methods: {
forget(){
//使用vuex,修改状态值
this.setLoginPrePath({path:this.$route.path,title:'忘记密码'});
this.$router.go({path:'/index2/forget.json'});
},
submit(){
if(this.loginBtn === 3){
if(this.checked){
this.$http.post('/zhixiao/password.json',{password:this.password}).then(
(res)=>{
if(res.ok){
console.log("注册成功,正在跳转登录页面");
setTimeout(()=>{
//获取状态值,通过getter
var path = this.getLoginPrePath[0].path;
this.loginPrePathShift();
this.$router.go(path);
},1500);
}
},(res)=>{
console.log('网络错误,请稍后重试');
}
)
}else{
console.log('请选择同意用户协议');
}
}else{
console.log('请填写验证码');
}
}
}
}
</script>
上面的例子并无实际效果,是我从以前项目中拼出来的(vuex1.0),只是为了说明loginPrePath这个状态值在vuex中的使用方式。详细请看Vue官方文档。
React中的状态管理--Flux
React中官方提供的状态管理是Flux,但是貌似现在第三方开发的Redux更强大,但是相比较使用的难度和学习曲线就有点陡峭了。
个人感觉Flux和Vue中的vuex思想基本相同,因为Vuex就是借鉴的Flux。
所以说,现在再来说Flux就简单了。
回想一下,在vuex中如果我们想修改一个状态值,我们应该怎么办呢?
在组件中配置vuex对象属性里面的actions和getters属性数组,然后配置的实际上是调用了Actions中的方法,Actions作用是将修改操作派生给store中的mutations,mutations真正处理业务逻辑并且修改状态值。
其实Flux也是如此,只不过在vuex中的Actions层执行了一个dispatcher方法将状态操作转发给了mutations,在Flux中直接需要显示的配置一层Dispatcher层进行转发。并且实现方式有所不同,vuex中mutations中隐藏了一些事件触发的操作,而Flux中直接通过我们自己编写代码实现,毕竟Flux是年纪大了,不如小弟vuex年轻呀。
例子:
components.js
import React from 'react';
import MyButton from './MyButton.jsx';
import ButtonActions from '../actions/ButtonActions.js';
import ListStore from '../stores/ListStore.js';
export default React.createClass({
getInitialState() {
return {
items: ListStore.getAll()
}
},
createNewItem() {
ButtonActions.addNewItem('New Item');
},
componentDidMount() {
ListStore.addChangeListener(this._onChange);
},
componentWillUnmount() {
ListStore.removeChangeListener(this._onChange);
},
_onChange() {
this.setState({
items: ListStore.getAll()
})
},
render() {
return (
<MyButton onClick={this.createNewItem} items={this.state.items} />
)
}
});
ButtonActions.js:
import AppDispatcher from '../dispatchers/AppDispatcher.js';
export default {
addNewItem(text) {
AppDispatcher.dispatch({
actionType: 'ADD_NEW_ITEM',
text: text
})
}
}
AppDispatcher.js':
import { Dispatcher } from 'flux';
import ListStore from '../stores/ListStore.js';
let AppDispatcher = new Dispatcher();
AppDispatcher.register(action => {
switch( action.actionType ) {
case 'ADD_NEW_ITEM':
ListStore.addNewItemHandle(action.text);
ListStore.emitChange();
break;
}
});
export default AppDispatcher;
ListStore.js:
import { EventEmitter } from 'events';
export default Object.assign({}, EventEmitter.prototype, {
items: [],
getAll(){
return this.items;
},
addNewItemHandler(text) {
this.items.push(text);
},
emitChange() {
this.emit('change');
},
addChangeListener(callback) {
this.on('change', callback);
},
removeChangeListener(callback) {
this.removeListener('change', callback);
}
});
仔细按照例子走一遍工作流程,相信你就理解Flux实现状态管理的思想了。
路由
要想实现SPA,路由是个不可避免的话题,作为主流的MVVM框架,怎么可能没有官方路由呢,两者的路由也很相似,都是利用各自的组件实现思想来实现的。
Vue中的路由
还是先贴官方链接,简单易懂。
再给个例子(vue-router1.0),仔细看一下:
app.js
//引用component
import index from './components/index.vue';
import main from './components/main.vue';
import my from './components/my.vue';
//APP route
import Vue from './libs/vue.js';
import VueRouter from './libs/vue-router.js';
Vue.use(VueRouter);
router.map({
'/':{
component: index,
subRoutes:{
'/':{
component: main
},
'/my':{
name:'my',
component: my
},
'/results/:key':{
name:'results',
component:results
}
}
}
});
//启动router
router.start(App,'body');
index.vue
<template>
<div class="box">
<div class="index-container">
<router-view>
</router-view>
</div>
<footer id="footer">
<ul>
<li v-bind:class="getIndex == $index ? 'active' : ''"
v-for="data in navList"
v-on:click="changePage($index)"
v-link="{path:data.path,exact: true}">
<i class="iconfont">{{{data.icon}}}</i>
<p>{{{data.name}}}</p>
</li>
</ul>
</footer>
</div>
</template>
<script>
var Vue = require('../libs/vue.js');
var VueResource = require('../libs/vue-resource.js');
import {getIndex} from '../vuex/getters.js';
import {changeIndexPage} from '../vuex/actions.js';
Vue.use(VueResource);
export default {
vuex: {
actions:{
changeIndexPage
},
getters:{
getIndex
}
},
data(){
return {
cur: 0,
navList:[
{path:'/',icon:'',name:'主页'},
{path:'/lee',icon:'',name:'排行榜'},
{path:'/search',icon:'',name:'发现'},
{path:'/my',icon:'',name:'我的'}
]
}
},
methods:{
changePage:function(i){
this.changeIndexPage(i);
this.cur = this.getIndex;
}
}
}
</script>
大概就这样,感觉像是配置的感觉,其实这就是利用的vue中组件思想来实现的,详细看官方文档。
React中的路由
React中的路由只需要安装插件react-router即可。
再来看例子:
app.jsx
'use strict';
import '../styles/usage/page/app.scss';
import React from 'react';
import ReactDOM from 'react-dom';
// import router
import { Router, Route, hashHistory, IndexRoute, Redirect } from 'react-router';
// router components
import App from './components/router/router-app.jsx';
import TV from './components/router/router-tv.jsx';
import Shows from './components/router/router-show.jsx';
import ShowIndex from './components/router/router-show-index.jsx';
let app = document.getElementById('app');
let handleEnter = () => {
console.log('entered');
}
let handleLeave = () => {
console.log('leaved');
}
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="tv" component={TV}>
<IndexRoute component={ShowIndex}></IndexRoute>
<Route path="/shows/:id" onEnter={handleEnter} onLeave={handleLeave} component={Shows} />
<Redirect from="shows/:id" to="/shows/:id" />
</Route>
</Route>
</Router>
), app);
router-app.jsx:
'ure strict';
import React from 'react';
// import router
import { Link } from 'react-router';
export default React.createClass({
render() {
return (
<div>
<div>
<Link to="/">首页</Link> |
<Link to="/tv">电视剧</Link>
</div>
{this.props.children}
</div>
)
}
});
router-tv.jsx
'use strict';
import React from 'react';
export default React.createClass({
render() {
return (
<div>
{this.props.children}
</div>
)
}
});
router-show.jsx
'use strict';
import React from 'react';
export default React.createClass({
render() {
return (
<h1>
节目内容 {this.props.params.id}
</h1>
)
}
});
router-show-index.jsx
'use strict';
import React from 'react';
import { Link } from 'react-router';
export default React.createClass({
render() {
return (
<div>
<Link to="tv/shows/2">电视节目列表</Link>
</div>
)
}
});
例子很简单,但是将常用的路由操作基本都涵盖了。
总结
大概通过自己的理解,对比了一下Vue和React的一些主要概念和实现方式,主要是为了加深自己理解,有些东西自己水平有限,不好表述,大概就是堆砌一些知识点而已。