【原譯】:https://tech.evojam.com/2024/03/31/react-vs-angular2-the-fight-rages-on/
??google的Angular和Facebook的React是現(xiàn)在最流行(但不是只有兩個(gè))的瀏覽器端應(yīng)用開發(fā)工具,它們都是很優(yōu)秀的解決方案。愛掏網(wǎng) - it200.com然而angular 2仍然在beta版中,Google的一部分工程師已經(jīng)對它進(jìn)行測試了。愛掏網(wǎng) - it200.com使用react開發(fā)的應(yīng)用也很多,像instagram,netlfix,paypal等。愛掏網(wǎng) - it200.com
?殘忍的戰(zhàn)爭就要到來了。愛掏網(wǎng) - it200.com
第一滴血
已經(jīng)有一篇”血腥“的文章《Angular 2 versus React》(作者:Cory House)來比較angular2與react,它體現(xiàn)了兩者很多方面的亮點(diǎn),第一次對決已經(jīng)結(jié)束,但是大戰(zhàn)才剛剛開始。愛掏網(wǎng) - it200.com(譯者注:老外寫個(gè)文章真的一定要這么夸張嗎?哈哈~)
認(rèn)清你的對手
作為開發(fā)者,選擇angular還是react就像購買現(xiàn)成的電腦還是用現(xiàn)成的零件來組裝電腦一樣。愛掏網(wǎng) - it200.com
Cory House告訴我們:
angular 2 React
壓縮后764K 壓縮后151k
獨(dú)立的完整解決方案 簡單的視圖庫
很多angular特定的語法 javascript語法
很好的一致性(和typescript) 基本語法有點(diǎn)混淆
使用html和js jsx語法
綜合成熟穩(wěn)定的框架 發(fā)展迅速的開源庫
手動(dòng)debug,缺少完全的支持 jsx-很好的開發(fā)體驗(yàn)
對web components友好 有可能支持web componnets
靜態(tài)執(zhí)行 jsx-動(dòng)態(tài)執(zhí)行
我想補(bǔ)充的是react有很多優(yōu)秀的瀏覽器開發(fā)插件,然而并沒有看到angular 2的。愛掏網(wǎng) - it200.com
競技場
??為了比較這些前端的技術(shù),我做了一些TODO應(yīng)用。愛掏網(wǎng) - it200.com為了使問題更加簡單,我在兩個(gè)應(yīng)用中只使用了Redux core(受angular 2-introduction to Redux啟發(fā))。愛掏網(wǎng) - it200.com兩個(gè)都是使用typescript開發(fā)的,所以比較起來比較清晰些。愛掏網(wǎng) - it200.com你可以對比下代碼:
– Redux Core: https://github.com/evojam/redux-todo-lib – Angular2 App: https://github.com/evojam/angular2-redux-sample-app – React App: https://github.com/evojam/react-redux-sample-app
對抗
??兩者的核心都是一個(gè)component或是一個(gè)view單元。愛掏網(wǎng) - it200.com兩個(gè)都將你的app形成一個(gè)組件樹。愛掏網(wǎng) - it200.com它們都鼓勵(lì)將數(shù)據(jù)通過頂層傳遞給組件樹。愛掏網(wǎng) - it200.com根到葉子的數(shù)據(jù)流思路使我們開發(fā)的應(yīng)用”更靈活”,所以現(xiàn)在開始。愛掏網(wǎng) - it200.com
第一輪:功能組件
??在這個(gè)樹形結(jié)構(gòu)的基礎(chǔ)應(yīng)用中,每個(gè)頂層樹是一個(gè)組件,每個(gè)組件的特點(diǎn):
從父節(jié)點(diǎn)接受數(shù)據(jù)(稱之為輸入)
返回一個(gè)組件的子樹(視圖view)
??在angular2和React中,輸入都是通過子節(jié)點(diǎn)屬性(不是html屬性)從一個(gè)元素傳遞到它的子樹,兩種解決方案中,視圖view都可以理解為xml樹。愛掏網(wǎng) - it200.com
TodoList組件
??一個(gè)可復(fù)用、可選擇、簡單的todo list需要做到兩點(diǎn)–todos數(shù)組(我做的數(shù)組)和過濾的方式(要展示的數(shù)組)。愛掏網(wǎng) - it200.com所以我們的組件輸入可以是這樣的:
interface ITodoListProps {
todos: ITodo[];
filter: FilterType;
}
??而組件在任何地方都可以這樣使用:
– React
–Angular 2
下面是React組件的定義:
// src/components/todo-list.tsx
import { Todo } from './todo';
export function TodoList(props: ITodoListProps): JSX.Element {
return (
{todosFilter(props.todoList, props.filter)
.map(todo => (
))}
);
}
下面是Angulart2組件定義的版本:
// src/components/todo-list.ts
import { Todo } from './todo';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
directives: [Todo],
host: {'class':'todo-list'},
pipes: [TodosFilter],
selector: '[todoList]',
templateUrl: '/src/components/todo-list.html'
})
export class TodoList implements ITodoListProps {
@Input() todoList: ITodo[];
@Input() filter: FilterType;
}
// src/components/todo-list.html
??毫無疑問,React版本是沒有狀態(tài),更純正,更簡單,它使用data并返回dom,很好。愛掏網(wǎng) - it200.com
??這里可以運(yùn)行但是不能正確編譯,我不知道它是不是typescript支持性的原因或者React編碼輸入的錯(cuò)誤(如果知道錯(cuò)誤請給issue)。愛掏網(wǎng) - it200.com無論怎樣,它失敗了,我不得不在React組件基礎(chǔ)上切換成一個(gè)class。愛掏網(wǎng) - it200.comReact保存了狀態(tài)–就是組件成員的屬性–但是屬性仍然可以被當(dāng)做immutable的數(shù)據(jù)輸入:
// src/components/todo-list.tsx
import { Todo } from './todo';
export class TodoList extends Component
render(): JSX.Element {
return (
{todosFilter(this.props.todoList, this.props.filter)
.map(todo =>
);
}
}
??angular2 版本需要多得多的配置,對于這個(gè)簡單的應(yīng)用要干的事情太多了。愛掏網(wǎng) - it200.com這是因?yàn)閍ngular沒有將js和html混合在一起(directives,host,pipes,selector,templateUrl)和更多復(fù)雜的數(shù)據(jù)修改檢測機(jī)制(數(shù)據(jù)監(jiān)聽)。愛掏網(wǎng) - it200.com
selector是一種將組件js定義和模板元素綁定起來的方法–而在React它不需要因?yàn)閖s組件就是jsx組件元素。愛掏網(wǎng) - it200.com
pipes和directives用于通知組件哪些其它的組件、directives和pipes(內(nèi)置html filter)將要在模板中使用,而React中的js組件是直接在jsx模板中使用的。愛掏網(wǎng) - it200.comReact不提供任何無模板的directive(對我來說這是個(gè)問題,我后面會(huì)提到)或者內(nèi)聯(lián)的pipes(因?yàn)槲覀兛梢允褂眉僯s功能)。愛掏網(wǎng) - it200.com
templateUrl是大家都明白的,對吧?它被因?yàn)槭蔷€下引用的,但是我確實(shí)覺得從組件文件中分離出更大的模板是比較合理的做法。愛掏網(wǎng) - it200.com缺少實(shí)時(shí)編譯的模板檢查確實(shí)是angular的劣勢。愛掏網(wǎng) - it200.com
而host的出現(xiàn)是因?yàn)槟0邃秩局羞^程不大一樣
??React的組件功能(或是渲染方法)返回整個(gè)頂層模板包含的組件樹。愛掏網(wǎng) - it200.com
return (
{ todoList.map( todo =>
);
??在angular中,組件是與組件的根元素綁定的(通過上面提到的選擇器selector),這個(gè)組件的根元素就被稱為host,所以在angular模板中,我們只放入根元素的內(nèi)容:
// src/components/todo-list.html
??如果我們想添加一下東西(例如css的class,屬性值)給host元素,我們稱之為host定義,例如 {'class':'todo-list'} 會(huì)添加 todo-list類到
Todo 組件
??所以我們現(xiàn)在開始綁定一些事件處理器。愛掏網(wǎng) - it200.comangular todo組件就像這樣:
// src/components/todo.ts
interface ITodoProps {
todo: ITodo;
}
@Component({
host: {'class': 'todo'},
selector: '[todo]',
templateUrl: '/src/components/todo.html'
})
export class Todo implements ITodoProps {
constructor(private todoActions: TodoActions) {}
@Input() todo: ITodo;
@HostBinding('class.done')
private get isCompleted() {
return this.todo.completed;
}
}
// src/components/todo.html
??我們把ITodo實(shí)例當(dāng)做一個(gè)輸入值。愛掏網(wǎng) - it200.com輸出為帶有兩個(gè)按鈕的host元素。愛掏網(wǎng) - it200.com我們也綁定了靜態(tài)todo和條件的"done"css類到host元素中。愛掏網(wǎng) - it200.com按鈕點(diǎn)擊觸發(fā)組件的todoActions成員相應(yīng)的方法。愛掏網(wǎng) - it200.com沒什么亮點(diǎn),只有(click)=...或許會(huì)引起我們的注意。愛掏網(wǎng) - it200.com我們也可以”加點(diǎn)糖“并使用
??而React版本:
interface ITodoProps {
todo: ITodo;
key: string;
}
export class Todo extends Component
private getToggleAction(todo: ITodo) {
return () => {
todoActions.toggleTodo(todo.id);
}
}
private removeTodo(todo: ITodo) {
todoActions.removeTodo(this.props.todo.id);
}
public render(): JSX.Element {
return (
);
}
}
??我們把ITodo實(shí)例當(dāng)做一個(gè)輸入值。愛掏網(wǎng) - it200.com輸出為帶有兩個(gè)按鈕的一個(gè)元素。愛掏網(wǎng) - it200.comXML樹返回的根元素也綁定了靜態(tài)todo和條件的"done"css類。愛掏網(wǎng) - it200.com目前為止都基本相似。愛掏網(wǎng) - it200.com但是我們看到這里兩個(gè)React令人失望的特點(diǎn):
在onClick處理中this的上下文丟失了–我們必須做下修改(綁定或添加)
class屬性必須和整個(gè)元素一起保存–沒有css類管理機(jī)制
??我也不喜歡className和htmlFor屬性,但是他們必須要使用,因?yàn)閏lass和For都是js的保留字,就像我們必須將html和js混合在一起一樣。愛掏網(wǎng) - it200.com
組合:React
Todo
TodoList
{ todoList.map( todo =>
對于對象更深的結(jié)構(gòu):
輸出為:
…
組合:Angular2
Todo
@Component({ host: {'class': 'todo'}, selector: '[todo]', … })
export class Todo … {
…
@HostBinding('class.done')
private get isCompleted() { … }
}
TodoList
@Component({ host: {'class':'todo-list'}, selector: '[todoList]', … })
export class TodoList … { … }
對于對象更深的結(jié)構(gòu):
輸出為:
…
第一輪結(jié)果
??React在開發(fā)輕量級(jí)單一組件場景下具有絕對優(yōu)勢。愛掏網(wǎng) - it200.com如果你的應(yīng)用可以用數(shù)據(jù)視圖簡單描述清楚的話React似乎是最佳的解決方案。愛掏網(wǎng) - it200.com而且無疑是我們見過的最靈活的視圖渲染框架。愛掏網(wǎng) - it200.com但是事件處理邏輯越多,UI渲染越復(fù)雜,angular2就越具有優(yōu)勢。愛掏網(wǎng) - it200.com事實(shí)上,angular2的組件配置和綁定聲明復(fù)雜度和組件的復(fù)雜性成反比。愛掏網(wǎng) - it200.com此外,它還有靈活的使用方式(從數(shù)據(jù)到視圖的綁定)。愛掏網(wǎng) - it200.com可能某一天Reactangular或者Angulareact框架能讓我們高效的在一個(gè)應(yīng)用中使用兩種實(shí)現(xiàn)方式。愛掏網(wǎng) - it200.com但現(xiàn)在我們必須要選擇。愛掏網(wǎng) - it200.com
??靈活與否,都應(yīng)該以減輕開發(fā)者的痛苦來作為考量。愛掏網(wǎng) - it200.com代碼的可讀性也是一方面,但目前html在這里是很重要的判斷。愛掏網(wǎng) - it200.com
第二輪:視圖美學(xué)
??讓我們來準(zhǔn)備一個(gè)新的組件來處理更復(fù)雜的結(jié)構(gòu)–列表結(jié)構(gòu)。愛掏網(wǎng) - it200.com
angular2
[class.active]="isCurrent(todoList)"
class="todo-lists-list-item">
- ngIf="!isCurrent(todoList)" class="todo-list">
-
class="todo-preview"
[class.done]="todo.completed">
React
-
className={this.listClassName(list)}>
{list.id !== this.props.currentId ? (-
className={this.todoClassName(todo)}>
{todo.text}
{list.todos.map(todo => (
))}
) : (
)} -
className={this.todoClassName(todo)}>
{this.props.lists.map(list => (
))}
第二輪結(jié)果
??有什么好說的嗎?選擇你喜歡的就好了~
??ok,我認(rèn)為
第三輪:數(shù)據(jù)修改監(jiān)聽
??我們在此聲明一件重要的事情–Dom修改(在修改被檢測到以后)在兩種框架中處理方式是類似的。愛掏網(wǎng) - it200.com它們只修改確實(shí)需要改變的部分。愛掏網(wǎng) - it200.com我們來看下如果發(fā)生數(shù)據(jù)變化,處理的方式有什么不同;
React
??React的基本處理方式很簡單。愛掏網(wǎng) - it200.com如果組件的state或者props發(fā)生改變,就會(huì)調(diào)用修改處理函數(shù):
state在setState()被調(diào)用時(shí)觸發(fā)
props在父組件重新渲染時(shí)發(fā)生變化
??當(dāng)然也可以通過調(diào)用forceUpdate()來觸發(fā)改變,修改監(jiān)聽器只在子樹發(fā)生改變的地方觸發(fā)。愛掏網(wǎng) - it200.com簡單便捷,唯一不好的地方是我們必須在頂層元素里手動(dòng)調(diào)用setState()方法。愛掏網(wǎng) - it200.com
??你可能會(huì)注意到例子里面的key={todo.id},這里是來列表元素檢測改變的機(jī)制必須的,如果一個(gè)key上面的值修改了–html相對應(yīng)的元素就會(huì)重新渲染。愛掏網(wǎng) - it200.com有點(diǎn)啰嗦,但是有必要。愛掏網(wǎng) - it200.com另外在項(xiàng)目中使用嚴(yán)格模式編程是,你必須在你的列表組件中額外定義一個(gè)key:string。愛掏網(wǎng) - it200.com
Angular
??angular 團(tuán)隊(duì)決定使用稍微不同的方式。愛掏網(wǎng) - it200.com他們包含了zone.js到瀏覽器異步回調(diào)中(例如:setTimeout,setInterval,事件處理和xhr請求事件)。愛掏網(wǎng) - it200.com當(dāng)他們當(dāng)中任意一個(gè)被調(diào)用,就會(huì)運(yùn)行修改檢測。愛掏網(wǎng) - it200.com你可參考下great in-depth explanation,更有趣的是,你可以在你任意一個(gè)組件樹上選擇使用的檢測策略。愛掏網(wǎng) - it200.com
??在樣例中,我使用了onPush策略,所以所有的組件修改檢測只在他們@input()屬性發(fā)生變化時(shí)才會(huì)觸發(fā)。愛掏網(wǎng) - it200.com所以angular里的修改檢測可以在任何合適的時(shí)候觸發(fā),而react只在一個(gè)時(shí)候觸發(fā)。愛掏網(wǎng) - it200.com當(dāng)然angular還有更多的觸發(fā)策略:CheckOnce, Checked, CheckAlways, Detached, OnPush, Default。愛掏網(wǎng) - it200.com跟多信息可以參考:ChangeDetectionStrategy docs。愛掏網(wǎng) - it200.com
??就像React的key={…}一樣,angular有自己的方式來處理列表的變化–NgFor.ngForTrackBy。愛掏網(wǎng) - it200.com在代碼里就是
第三輪結(jié)論
??兩種解決方案都提供了一個(gè)相對健全的途徑,然而React的默認(rèn)修改檢測無疑更優(yōu)一些。愛掏網(wǎng) - it200.com另一方面,angular這種在底層的檢測相當(dāng)于一個(gè)瀏覽器開發(fā)玩家使用的因?yàn)樗褭z測插入到瀏覽器一些異步調(diào)用層,所以廣播檢測的職責(zé)對開發(fā)者來說不可控。愛掏網(wǎng) - it200.com當(dāng)然每種方案都有他們不盡人意的地方。愛掏網(wǎng) - it200.comReact的組件檢測如果數(shù)據(jù)不是來自屬性的話必須通過手動(dòng)調(diào)用觸發(fā),如果使用了其它存儲(chǔ)方式會(huì)直接觸發(fā)(Flux 框架)。愛掏網(wǎng) - it200.com如果你想讓angular變得靈活些,你必須在每個(gè)單一的組件定義他們的數(shù)據(jù)檢測觸發(fā)策略。愛掏網(wǎng) - it200.com
??不過兩種方式都很好。愛掏網(wǎng) - it200.comangular使用簡潔的配置工具實(shí)現(xiàn)了更多的擴(kuò)展性,這點(diǎn)React就沒做到。愛掏網(wǎng) - it200.com
第四輪:擴(kuò)展html
??做什么?就是像html添加一些功能,讓它在更多的元素中被復(fù)用。愛掏網(wǎng) - it200.com
Angular
@Directive({selector: '[inp-alerter]'})
export class InpAlerter {
@Input('inp-alerter') inpAlerter: string;
@HostBinding() placeholder = 'Write something here';
@HostListener('keyup.enter', ['$event.target.value'])
onInput(val: string): void { … }
}
??上面的例子展示了我們怎樣從一個(gè)輸入屬性中獲取值、綁定一些其他值給元素屬性或給元素添加監(jiān)聽事件。愛掏網(wǎng) - it200.com而且它可以用inp-alerter屬性應(yīng)用到其它任意一個(gè)html元素中。愛掏網(wǎng) - it200.com而且這只是我們能做的很小的一部分,實(shí)際上@Directive定義的類可以作為絕大多數(shù)功能用于@components,但是沒有模板。愛掏網(wǎng) - it200.com簡單強(qiáng)大。愛掏網(wǎng) - it200.com
React
??有一種可能是使用React mixins的方式來實(shí)現(xiàn)相同的功能,但是有點(diǎn)過度設(shè)計(jì)的傾向而且容易出錯(cuò)。愛掏網(wǎng) - it200.com無論怎樣,開發(fā)者必須為應(yīng)用中使用的每個(gè)元素功能創(chuàng)建一個(gè)組件。愛掏網(wǎng) - it200.com
K.O.
??最后一輪決出了勝負(fù),React被戰(zhàn)勝了。愛掏網(wǎng) - it200.com
??感謝上帝,這只是個(gè)比喻。愛掏網(wǎng) - it200.com
??總之,這是一個(gè)angular 1.x統(tǒng)治的世界,而且也正準(zhǔn)備走向html擴(kuò)展元素web components的世界。愛掏網(wǎng) - it200.comangular 2贏的了這一輪。愛掏網(wǎng) - it200.com但是我確定React不會(huì)就此放棄。愛掏網(wǎng) - it200.com
落幕
??顯然,React和Angular 2有很多共同點(diǎn),他們在處理應(yīng)用框架和數(shù)據(jù)上使用了相似的原理。愛掏網(wǎng) - it200.com另一方面,每個(gè)功能的實(shí)現(xiàn)都使用了不同的方式(好吧,組件調(diào)用的生命周期還是完全一致的)。愛掏網(wǎng) - it200.com這些不同點(diǎn)并不意味著應(yīng)用開發(fā)時(shí)的難度,每種方案都提供了足夠完善的工具來開發(fā)一個(gè)大型、嚴(yán)謹(jǐn)、靈活的應(yīng)用核心。愛掏網(wǎng) - it200.com
??當(dāng)然React更小并且只涵蓋view/component的操作–這是我這里要對比的。愛掏網(wǎng) - it200.com缺少向html的擴(kuò)展機(jī)制無疑是React唯一不足的地方。愛掏網(wǎng) - it200.com
??Angular2則更加穩(wěn)定、可擴(kuò)展和更加完善。愛掏網(wǎng) - it200.com但是它仍然在beta階段–并且相對對手具有優(yōu)勢因?yàn)闊o論相比angular1還是React的經(jīng)歷來看它具有更加優(yōu)秀的合并思想。愛掏網(wǎng) - it200.com
原文作者:JAKUB STROJEWSKI
參考文章:
Comparison of Angular 2 and React
State Management in Angular 2 and React
Choosing between React vs. Angular 2