在构建html元素时,vue倾向于模板方式,而react则完全使用javascript的编程能力,但vue也具备完全编程的能力(与react一样使用JSX和createElement渲染函数)。所以,当vue使用完全编程方式时,与react可以说是大同小异。
学习react的时候,有一个核心思想:组件就是函数,元素就是值。这对于vue来说也是完全适用的,唯一的区别在于两者的限制和使用条件不同。
目录
- JSX
- 元素渲染
- 组件定义
- 组件传值
- 内容分发
- 数据响应
- 事件处理
JSX
JSX是JavaScript的一个扩展语法,本质是createElement渲染函数的语法糖。react和vue在JSX语法上几乎一致,区别在于属性值的传入上。
react的JSX语法更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase
(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。
例如,JSX 里的 class
变成了 className
,而 tabindex
则变为 tabIndex
。
vue的JSX则使用了一个Babel 插件使其更贴近HTML模板语法,vue对大小写不敏感(实际上vue会将所有属性值转换为小写)。
例如,tabIndex
会转化成tabindex
,className
会转化为classname
。
特例,对于属性class
,react应写成className
,vue应写成class
react引入了Fragments
使得react可以有多个根元素,vue没有此功能,只能是单一根元素
元素渲染
react的文档中提到了
元素是构成 React 应用的最小砖块。
const element = <h1>Hello, world</h1> // 定义元素...
render () {return <div>{element}</div> // 使用元素
}
react将元素当作值来对待,你可以直接使用,也可以先赋值给某个变量,然后再使用。
这一点在vue的文档中虽然没有明确提到,但依然适用,vue也可以先将元素赋值给某个变量,然后在适当的位置使用该变量。
两者在使用时的区别在于:
- react可以在任意位置(组件内和组件外)定义元素并使用
- vue只能在组件内定义元素(只要是组件内,任意位置都可以,render函数内,data数据或者其他位置),在组件外定义则会报错(
h is not defined
)
报错原因:网上找到的一个说法是vue中组件外没有上下文联系,找不到渲染函数。
思来想去只能归结于两者的渲染机制不同(没有依据,瞎猜的😜:react可能是先赋值后渲染,vue可能是先渲染后赋值)
组件定义
react定义组件有两种方式,函数组件和class组件,两者在一定条件下可以相互转换,函数式组件是无状态组件,没有state状态和生命周期
// 函数组件
cosnt MyComponent = props => {return <div>{props.title}</div>
}// class组件
class MyComponent extends React.Component {static defaultProps = {} // 设置默认传入值constructor(props) {super()this.state = {} // 设置状态值}... // 其他组件函数:自定义方法,生命周期方法...render() {return <div>{this.props.title}</div>}
}
vue文档给出的组件定义只有一种方式(如下),函数式组件(无状态)是在普通组件的基础上添加functional
标记
Vue.component('my-component', {props: {title:String},...render () {return <div>{this.title}</div>}
})
实际上,上诉方式是官方给出的一种完整使用(组件定义和全局注册),在实际项目中通常组件定义和注册都是分开的(即组件的局部注册)。
而react没有组件注册这一说法,只有定义和使用,vue实际上也可以和react一样,直接定义并使用,前提是使用JSX语法,如果使用createElement渲染函数和模板方式,仍然需要进行组件注册。
vue定义组件方式如下
// 函数式组件
const MyComponent = {functional: true, // 函数式组件标记,props: {title:String}, // 设置传入值// 为了弥补缺少的实例,提供第二个参数作为上下文render(h, context) {return <div>{this.props.title}</div>}
}
// 函数式组件的变种写法 -- 函数方式,对照了react的函数组件的写法
// vue官方文档中没有提到这种写法,但这种写法依然生效
// 与react函数式组件的不同之处在于,react函数式组件的传入值是props,vue函数组件传入值是context上下文
cosnt MyComponent = context => {return <div>{context.props.title}</div>
}// 普通组件
const MyComponent = {props: {title:String}, // 设置传入值data () { return { user: '' } }, // 数据computed: {}, // 计算属性watch: {}, // 监听属性methods: {}, // 自定义方法... // 其他生命周期方法,钩子函数等...render() {return <div>{this.props.title}</div>}
}
从上面react和vue的代码片段中可以看出,react定义组件用的是class对象
,vue定义组件用的是Object对象
,两者的区别在于:
class对象
方法之间不需要分隔符,Object对象
的属性之间需要逗号,
分隔- 对于传入值,两者都是使用的
props
,react只能定义默认值,vue则可以限制传入类型并进行数据验证(react要实现类型验证需要引入prop-types
) - 对于状态值,react将状态值放入
class对象
的属性值state
里,并在constructor
构造函数中完成数据初始化,vue则将状态值放入data方法
的返回值中 - 对于自定义方法,react可直接书写在
class对象
下,vue则必需放入methods
字段中 - 数据监听,react在生命周期方法
componentDidUpdate
中监听数据变化,vue则提供了更为方便的computed
和watch
组件传值
react和vue的组件传值都是通过组件属性进行传递的。
- react,不管是函数式组件还是普通组件,将组件的所有属性都当作
prop
传入; - vue,对于函数式组件
- 版本2.3.0之前,如果一个函数式组件想要接收
prop
,则props
选项是必须的; - 版本2.3.0或以上,如果定义了
props
选项,则只能接收定义的prop
; - 版本2.3.0或以上,如果省略
props
选项,组件上所有的attribute
都会被自动隐式解析为prop
;
- 版本2.3.0之前,如果一个函数式组件想要接收
- vue,对于普通组件
- 特殊属性class、style,直接挂载到组件根元素上,与组件内的class和style叠加合并;
- 组件上的属性如果在
props
中的定义了,则作为prop
传入; - 对于没有声明的属性,默认自动挂载到组件根元素上,并覆盖原来的属性值(可通过设置
inheritAttrs
属性开关此功能,默认开启);
内容分发
说完组件传值,那就不得不说组件的内容分发,这是一种特殊的组件传值(其本质还是组件传值)。
在vue中,内容分发使用的是插槽slot
,react则称为组件组合,并且react特意强调了
React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递
最常见的就是如下以子节点(children)方式
<MyComponent>hello world</MyComponent>
- react提供了一个特殊的
children
prop用来接收分发内容, - vue函数式组件即可以通过
children
来接收内容,也可以通过slots().default
方式, - vue普通组件只能通过
$slots.default
的方式分发
vue函数式组件和普通组件对插槽slot
使用的区别主要式因为函数式组件没有实列this
,而是使用上下文context
,详见vue函数式组件
vue的插槽分为普通插槽slots
和作用域插槽scopedSlots
,作用域插槽可以完全替代普通插槽,建议使用使用scopedSlots
简单的示列如下
// react 函数式组件
const MyComponent = props => {return <div>{props.children}</div>
}
// react class组件
class MyComponent extends React.Component {render() {return <div>{this.props.children}</div>}
}// vue 函数式组件
const MyComponent = context => {// children方式分发const { children } = contextreturn <div>{children}</div>// 通过插槽方式分发// const { slots } = context// return <div>{slots().default}</div>
}
// vue 普通组件
const MyComponent = {render () {return <div>{this.$scopedSlots.default()}</div>}
}
多内容分发
- react只需要传入对应的
prop
即可 - vue使用
slots
或scopedSlots
进行分发
// react
...render () {return (<div><header>{props.header}</header><main>{props.children}</main><footer>{props.footer}</footer></div>)}
...
// react使用
<MyComponentheader='hello world'chilren={<p>Lorem ipsum dolor sit amet</p>}footer='this is footer'
/>// vue
...render () {return (<div><header>{this.$scopedSlots.header()}</header><main>{this.$scopedSlots.default()}</main><footer>{this.$scopedSlots.footer()}</footer></div>)}
...
// vue使用
<MyComponentscopedSlots={{ header: props => `hello, world`,default: props => <p>Lorem ipsum dolor sit amet</p>,footer: props => 'this is footer'}}
/>
对于内容分发,react显得十分灵活及简便,vue在使用上相对有部分限制,vue可以使用作用域插槽,react只能通过其他方式实现类似功能。vue插槽的JSX使用方式
实际上,如果vue不使用插槽方式,改用props
也可以和react一样实现内容分发
// vue 使用 props 实现了内容分发,定义和使用方式和 react 几乎一样
const MyComponent = {props: ['header', 'children', 'footer'],render () {return (<div><header>{this.header}</header><main>{this.children}</main><footer>{this.footer}</footer></div>)}
}export default {render () {return (<MyComponentheader='hello, world'children={<p>Lorem ipsum dolor sit amet</p>}footer={'this is footer'}/>)}
}
数据响应
响应式数据更新方式是vue与react的一个重要区别之一
- vue的数据加入到响应式系统中,直接操作数据会同步更新视图
- react必需使用
setState
方法更新数据以便实现视图的同步更新
事件处理
react的事件处理为了在回调中使用 this
,必须为该方法绑定this
(在构造函数中绑定或在事件处理程序传递参数),vue则不需要这个绑定过程。