篇文章是為ReactJs小白準備的,希望他們快速抓住ReactJs的要點并能在實踐中隨機應變。
ReactJs是一個專注于View的Web前端框架。Web前端的View就是瀏覽器中的Dom元素,改變View的唯一途徑就是修改瀏覽器中的Dom元素,因此ReactJs的核心任務就是如何修改Dom元素,作為一個成功的框架,ReactJs使修改Dom元素變得高效而又簡單。
ReactJs把修改Dom的操作簡化成一個函數renderInto(parentDom, props, states)=>htmlString,這個函數的意圖就是根據props,states計算出視圖對應的html字符串并添加為parentDom的子節點。props和states就是普通的javascript對象,這個函數的核心邏輯就是計算html元素的機構及元素屬性然后拼接成字符串返回。作為框架,ReactJs用JSX形式的DSL解決了拼接html的任務并接管了更新到parentDom的職責。看一個例子,理解這個函數并理解ReactJs怎么使用這個函數你就可以一個人開始ReactJs之旅了。
var props={name: 'myname'};
var states={amount: 1000};
function render(props, states) {
var title=’Hello, ' + props.name;
var description='You have ' + states.amount + ' score now.';
return (
<div className="score-board">
<h1>{title}</h1>
<p>{description}</p>
</div>
);
}
函數第一行根據props計算title,第二行根據states計算description,最后以JSX形式返回拼接好的html字符串。
如果你用過AngularJs,EmberJs等類似的前端框架,你可能會覺得沒什么了不起,不就是把模板和邏輯放到一起嗎?是的,沒錯,但這不僅僅是組織形式上的改變,而是編程隱喻的轉變—從復雜的MVC或MVVM模式到簡單的render函數。還有一點不同是JSX最終編譯成調用react-dom的javascript語句,而不是直接生成字符串。
render函數還只是ReactJs這座冰山的一角,”React”會在render函數的輸入變化時再次調用這個函數。再看一個例子。
var props={name: 'myname'};
var states={amount: 1000};
function handleClickAdd() {
states={amount: states.amount + 1};
}
function render(props, states) {
var title=’Hello, ' + props.name;
var description='You have ' + states.amount + ' score now.';
return (
<div className="score-board">
<h1>{title}</h1>
<p>{description}</p>
<button onClick={handleClickAdd}>+1</button>
</div>
);
}
這個例子增加了一個”+1”按鈕,當用戶點擊按鈕時會修改states,ReactJs在states變化時的”React”就是再次調用render函數,然后用新輸出更新瀏覽器的dom。
可能你會問,props和states不就是Model嗎?是的,可以理解成Model,但此Model非彼Model,props和states都是為View服務的而非和View平起平坐。
可能你還會問,為啥不把props和states合并成一個對象?要回答這個問題,就涉及到復雜視圖的場景。想想看,當視圖內的元素不斷增加時,代碼上如何處理,還要在一個render函數里折騰嗎?肯定不會。我猜你已經想到了,要把大問題拆小。ReactJs給出的解決方法就是把大視圖拆成若干個小視圖,每個視圖都有自己的render函數,在JSX中可以直接使用視圖標簽。看一個例子。
var Score=React.createClass({
initialState: function() {
return {amount: 1000};
},
function handleClickAdd() {
this.setState({amount: this.states.amount + 1});
}
render: function() {
var title=’Hello, ' + this.props.name;
var description='You have ' + this.states.amount + ' score now.';
return (
<div className="score-board">
<h1>{title}</h1>
<p>{description}</p>
<button onClick={handleClickAdd}>+1</button>
</div>
);
}
});
var ScoreList=React.createClass({
render() {
return (
<ul className="score-list">
<li><Score name="Tom" /></li>
<li><Score name="Jerry" /></li>
</ul>
);
}
});
ReactDOM.render(
<ScoreList />,
document.getElementById('content')
);
這個例子中有兩類View,分別是Score和ScoreList。ScoreList的render函數中使用Score標簽并給出配置項name的值。詳細看一下Score,ReactJs提供createClass方法定義視圖,在render函數中通過this.props訪問外部傳入的配置項,通過this.states訪問視圖內部的狀態。從意圖上看,props外部傳入視圖的配置項,擁有者是父視圖,視圖內部只能讀取配置項,states的擁有者是視圖自身。
區分props和states的結果就是,子視圖沒辦法直接改變父視圖,視圖改變一定是自觸發改變的視圖開始向子視圖傳播。對上面的例子,當Tom的Score改變時,ScoreList其他部分一定不會改變,所以視圖更新從Tom的Score視圖開始就可以,這就保證了能更高效地計算視圖變化,再加上VirtualDom的使用,使ReactJs的效率大大超過其他框架。
當子視圖需要改變父視圖時,也一定是從父視圖開始向下更新。假如上面的例子中ScoreList還有平均分的視圖,當Tom的分數改變時,需要更新ScoreList中的平均分。這就需要Score視圖在處理”+1”輸入時把變化通知到ScoreList,做法時給Score增加配置項,ScoreList中定義更新平均分的函數并把函數作為配置項傳給Score。當ScoreList更新時,因為Jerry的props和states都沒變化,所以Jerry的Score視圖不需要更新。
這就是ReactJs的全部秘密了(不過Web前端本身是一個復雜系統,你還需要了解更多其他內容)。
ngularJS的主要組成部分是:
啟動
我們通過一個例子來講解啟動這個部分
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>
</head>
<body>
<p ng-init=" name='World' ">Hello {{name}}!</p>
</body>
</html>
執行期
瀏覽器的事件機制:
而AngularJS通過使用自己的Event loop,改變了傳統的Javascript工作流。這使得Javascript的執行被分成原生部分和擁有AngularJS執行上下文的部分。只有在AngularJS執行上下文中運行的操作,才能享受到AngularJS提供的數據綁定,異常處理,資源管理等功能和服務。你可以使用 $apply()方法,從普通Javascript上下文進入AngularJS執行上下文。記住,大部分情況下(如在控制器,服務中),$apply都已經被執行過了。只有當你使用自定義的事件回調或者是使用第三方類庫的回調時,才需要自己執行$apply。
下面通過一個例子來講解如何實現“將用戶輸入綁定到視圖上”的效果。
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>
</head>
<body>
<input ng-model="name">
<p>Hello {{name}}!</p>
</body>
</html>
在編譯階段:
input元素上的ng-model指令會給<input>輸入框綁定keydown事件;
{{name}}這個變量替換表達式建立了一個 $watch ,來接受 name 變量改變的通知。
在執行期階段:
按下任何一個鍵(以X鍵為例),都會觸發一個 input 輸入框的keydown事件;
input 上的指令捕捉到 input 內容的改變,然后調用 $apply("name='X';")來更新處于AngularJS執行上下文中的模型;
AngularJS將 name='X'應用到模型上;
$digest 循環開始;這個循環是由兩個小循環組成的,這兩個小循環用來處理$evalAsync隊列和$watch列表。這個$digest循環直到模型“穩定”前會一直迭代。這個穩定具體指的是$evalAsync列表為空,并且$watch列表中檢測不到任何改變了。這個$evalAsync隊列是用來管理那些“視圖渲染前需要在當前棧外執行的操作”。這通常使用 setTimeout(0)來完成的。并且,因為瀏覽器會根據事件隊列按順序渲染視圖,這時還會造成視圖的抖動。$watch列表是一個表達式的集合,這些表達式可能是自上次迭代后發生了改變的。如果檢測到了有改變,那么$watch函數就會被調用,它通常會把新的值更新到DOM中。
$watch 列表檢測到了name值的變化,然后通知 {{name}}變量替換的表達式,這個表達式負責將DOM進行更新;
AngularJS退出執行上下文,然后退出Javascript上下文中的keydown事件;
瀏覽器以更新的文本重新渲染視圖。
作用域(Scope)
作用域是用來檢測模型的改變和為表達式提供執行上下文的。它是分層組織起來的,并且層級關系是緊跟著DOM的結構的。
下面這個例子演示了{{name}}表達式在不同的作用域下被解析成了不同的值
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>
<script>
function GreetCtrl($scope) {
$scope.name='World';
}
function ListCtrl($scope) {
$scope.names=['Igor', 'Misko', 'Vojta'];
}
</script>
</head>
<body>
<div ng-controller="GreetCtrl">
Hello {{name}}!
</div>
<div ng-controller="ListCtrl">
<ol>
<li ng-repeat="name in names">{{name}}</li>
</ol>
</div>
</body>
</html>
在GreetCtrl控制器中的name等于'World'。在ListCtrl控制器中的name等于'Igor', 'Misko', 'Vojta'。因為它們的作用域不一樣。
控制器
視圖背后的控制代碼就是控制器。它的主要工作內容是構造模型和回調方法,并把模型和回調方法一起發送到視圖。 視圖可以看做是作用域在模板(HTML)上的投影。而作用域是一個中間地帶,它把模型整理好傳遞給視圖,把瀏覽器事件傳遞給控制器。控制器和視圖的分離非常重要,因為:
(1)控制器是由Javascript寫的。Javascript是命令式的,命令式的語言適合用來編寫應用的行為。控制器不應該包含任何關于渲染代碼(DOM引用或者片段)。
(2)視圖模板是用HTML寫的。HTML是聲明是的,聲明式的語言適合用來編寫UI。視圖不應該包含任何行為。
(3)因為控制器和視圖沒有直接的調用關系,所以可以使多個視圖對應同一個控制器。這對“換膚(re-skinning)”、適配不同設備(比如移動設備和臺式機)、測試,都非常重要。
模型
模型就是用來和模板結合生成視圖的數據。模型在作用域中可以被引用,這樣才能被渲染生成視圖。和其他框架不一樣的是,Angularjs對模型本身沒有任何限制和要求。你不需要繼承任何類也不需要實現指定的方法。 模型可以是哈希形式的原生對象,也可以是完整對象類型。簡而言之,模型可以是原生的Javascript對象。
視圖
所謂視圖,就是指用戶所看見的。 視圖的生命周期由作為一個模板開始,它將和模型合并,并最終渲染到瀏覽器的DOM中。與其他模板系統不同的是,AngularJS使用一種獨特的形式來渲染視圖。
其他模板 - 大部分模板系統工作原理,都是一開始獲取一個帶有特殊標記的HTML形式字符串。通常情況下模板的特殊標記破壞了HTML的語法,以至于模板是不能用HTML編輯器編輯的。然后這個字符串會被送到模板引擎那里解析,并和數據合并。合并的結果是一個可以被瀏覽器解析的HTML字符串。這個字符串會被.innerHTML方法寫到DOM中。使用innerHTML會造成瀏覽器的重新渲染。當模型改變時,這整個流程又要重復一遍。模板的生存周期就是DOM的更新周期。這里我想強調是,這些模板的基礎是字符串。
AngularJS - AngularJS和其它模板系統不同。它使用的是DOM而不是字符串。模板仍然是用HTML字符串寫的,并且它仍然是HTML。瀏覽器將它解析成DOM, 然后這個DOM會作為輸入傳遞給模板引擎,也就是我們的編譯器。編譯器查看其中的指令,找到的指令后,會開始監視指令內容中相應的模型。 這樣做,就使得視圖能“連續地”更新,不需要模板和數據的重新合并。你的模型也就成了你視圖變化的唯一原因。
指令
一個指令 就是一種“由某個屬性、元素名稱、css類名出現而導致的行為,或者說是DOM的變化”。指令能讓你以一種聲明式的方法來擴展HTML表示能力。
Filters過濾器
過濾器扮演著數據翻譯的角色。一般他們主要用在數據需要格式化為本地格式的時候。它參照了UNIX過濾的規則,并且也實現了“|”(管道)語法。
模塊和注入器
每個AngularJS應用都有一個唯一的注入器。注入器提供一個通過名字查找對象實例的方法。它將所有對象緩存在內部,所以如果重復調用同一名稱的對象,每次調用都會得到同一個實例。如果調用的對象不存在,那么注入器就會讓實例的工廠(instance factory)函數創建一個新的實例。
一個模塊就是一種配置注入器的實例的工廠函數的方式,我們也稱它為“提供者(provider)”。
var myModule=angular.module('myModule', [])
myModule.factory('serviceA', function() { //定義serviceA的工廠函數,myModule模塊就是提供serviceA實例的工廠函數的提供者
return {
......
};
});
// create an injector and configure it from 'myModule'
var $injector=angular.injector('myModule');
// retrieve an object from the injector by name
var serviceA=$injector.get('serviceA'); //從注入器查找serviceA對象,這時注入器會讓實例serviceA的工廠函數factory創建一個新的實例serviceA返回
// always true because of instance cache
$injector.get('serviceA')===$injector.get('serviceA');
注入器真正強大之處在于讓方法和類型能夠通過注入器,請求到他們依賴的組件,而不需要自己加載依賴。
我們看看下面動態時間的這個例子:
<!doctype html>
<html ng-app="timeExampleModule">
<head>
<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>
<script">
angular.module('timeExampleModule', []).
factory('time', function($timeout) {
var time={};
(function tick() {
time.now=new Date().toString();
$timeout(tick, 1000);
})();
return time;
});
function ClockCtrl($scope, time) {
$scope.time=time;
}
</script>
</head>
<body>
<div ng-controller="ClockCtrl">
Current time is: {{ time.now }}
</div>
</body>
</html>
你只要把需要的依賴寫在函數參數里。當AngularJS調用這個函數時,它會自動填充好需要的參數。這個例子中,當ng-controller實例化構造器ClockCtrl的時候,它自動提供了指明的依賴time實例對象。
AngularJS 命名空間
為了防止意外的命名沖突, AngularJS為可能沖突的對象名加以前綴"$"。所以請不要在你自己的代碼里用"$"做前綴,以免和AngularJS代碼發生沖突。
于一個Web前端工程師而言,框架知識非常重要,它對你項目的成功有著相當大的影響。專業的鄭州Web前端課程都會涵蓋JS框架知識,比如Angular和React,今天千鋒鄭州老師就給大家講解一下AngularJS與ReactJS的區別,以便同學們理解記憶。
AngularJS:框架領域的冠軍
Angular.js是一個開源的Web應用程序框架,具有由Google提供的Model-View-Controller(MVC)架構(Angular 1)和Model-View-ViewModel(MVVM)架構(Angular 2)。Angular.js通過使用指令擴展HTML的功能來解決開發SPA(單頁應用程序)的問題,此框架強調讓你的app快速完成和運行。目前Angular是最受歡迎的JS框架,你可以一站式使用,它是大型企業的首選框架。
ReactJS:在塊上的新生兒
ReactJS是一個開源的JavaScript庫,用于構建高性能的用戶界面,專注于由Facebook引入和提供的驚人的渲染性能。React專注于模型視圖控制器(Model View Controller)架構中的“V”,它是為了解決與其他JavaScript框架的常見問題——大數據集的高效渲染而創建的。React是最輕量級的JS框架,如果你需要逐漸現代化現有的代碼庫,那么這是一個合適的選擇。
Angular和React功能比較:
動態UI綁定:Angular允許在純對象或甚至屬性級別使用UI綁定。可以同時更新多個綁定,而不需要耗時的DOM更新;ReactJS直截了當地將狀態直接鏈接到UI。狀態參數作為對象傳遞,并合并到React組件的內部參考狀態。
可重復使用的組件:Angular組件稱為“指令”,它們比Ember組件強大得多。它們能夠創建你自己語義的和可重用的HTML語法;React在視圖和控制器級別使用mixin,因此組件不必UI相關,并且可能只包含一些實用程序或甚至復雜的程序邏輯。
路由:Angular需要模板或控制器到其路由器配置,必須手動管理;React不處理路由,但是有很多模塊用于路由,如react-router,flow-router。
意見:Angular和React都是靈活的意見,給出一點靈活性來實現你自己的客戶端堆棧。
數據綁定:Angular是雙向綁定,React是單向綁定。
要選擇哪個框架,需要評估應用程序的需求以及每個框架的優勢,需要深入了解所考慮的每個框架的優點和缺點,以及它們如何在不同用例下競爭。如果你正在決策創建一個Web app,對于長期支持和活躍的社區,Angular、React都是安全的。
如果你想了解更多Web前端課程內容,可以關注“千鋒鄭州校區”微信公眾號。如果你想參加專業的鄭州Web前端培訓班,可以來千鋒申請長達兩周的免費試聽,親身體驗教學效果!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。