在構建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則不需要這個綁定過程。