在接觸React以前,咱們也許習慣了DOM編程,那它相比於原生JS,JQ編程方式,究竟有什麼區別?React的工做方式是什麼樣子的?所謂的虛擬DOM又指的是什麼?以及React的工做方式的優勢有哪些?javascript
那麼本篇就是你想要知道的css
若是想閱讀體驗更好,可戳React學習(4)-理清React的工做方式,內有視頻vue
從一個簡單的React組件開始
咱們先看一個加減數字框組件,具體效果以下所示,分別經過原生JS和JQ是怎麼實現的java
<div>
<button id = "reduce">-</button>
<input id = "input" type="text" value="0">
<button id = "add">+</button>
</div>
複製代碼
CSS層疊樣式react
*{
padding: 0;
margin: 0;
}
div{
width: 100%;
display: flex;
display: -webkit-flex;
position:fixed;
left: 40%;
top:10%;
}
button {
padding: 10px;
}
input {
text-align:center;
}
複製代碼
對應的JSjquery
// 獲取DOM元素
var oBtnReduce = document.querySelector("#reduce"),
oInput = document.querySelector("#input"),
oBtnAdd = document.querySelector("#add");
// 添加事件
oBtnAdd.onclick = function() {
oInput.value++;
}
oBtnReduce.onclick = function() {
oInput.value--;
}
複製代碼
JQ實現:程序員
var $reduce = $('#reduce'),
$input = $('#input'),
$add = $('#add'),
$nowVal = $("#input").val();
$reduce.click(function() {
$input.val($nowVal--);
});
$add.click(function() {
$input.val($nowVal++);
})
複製代碼
固然,你把事件添加在內聯元素身上,能夠在行內元素裏面添加事件,經過傳參的方式去控制,以下代碼所示,也是能夠的web
<div>
<button onclick = "handleClick('-')" id = "reduce">-</button>
<input id = "input" type="text" value="0">
<button onclick = "handleClick('+')" id = "add">+</button>
</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script type="text/javascript">
function handleClick(flag) {
var nowVal = $("#input").val();
if(flag == '+') {
$("#input").val(parseInt(nowVal) +1);
}else if(flag == '-' ) {
$("#input").val(parseInt(nowVal) -1);
}
}
</script>
複製代碼
對於在原生JS,JQ中,經過內聯方式添加事件,是不推薦的,然而在現在的一些面向數據編程,例如React,Vue等框架中,這一方式卻獲得了支持與延續,要從面向DOM編程轉移到面向數據編程 React實現npm
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class CountNum extends Component {
constructor(props) {
super(props);
this.state = {
inputVal: 0
}
}
render() {
return (
<div style = {{ textAlign: "center", marginTop: "50px"}}>
<button onClick = { this.handleClickReduce.bind(this) }>-</button>
<input style = {{ textAlign: "center"}} value = { this.state.inputVal } onChange = { this.handleInputChange.bind(this) } />
<button onClick = { this.handleCLickAdd }>+</button>
</div>
);
}
handleCLickAdd = () => {
this.setState({
inputVal: this.state.inputVal+1
});
}
// handleCLickAdd(){
// this.setState({
// inputVal: this.state.inputVal+1
// })
// }
handleClickReduce() {
this.setState({
inputVal: this.state.inputVal-1
})
}
handleInputChange(e) {
let changeVal = e.target.value;
this.setState({
inputVal: changeVal++
});
}
}
const container = document.getElementById('root');
ReactDOM.render(<CountNum />, container);
複製代碼
從上面一看,對於剛接觸React的小夥伴來講,可能以爲用原生JS,JQ實現起來很簡單呀,React寫起來的代碼,什麼玩意的,那麼一大堆的,JS裏面還寫HTML代碼,簡直噁心到不行,並未達到,內容結構,層疊樣式,邏輯的分離,若是對於這部份內容有疑惑的,能夠閱讀以前兩篇JSX的文章的編程
對於JS,JQ的實現方式,主要工做是在操做DOM,獲取元素,添加事件,執行操做。對於簡單的業務實現,是沒有什麼問題的,可是當DOM結構層級比較深,要進行一些複雜的邏輯操做時,此時,不斷的操做DOM就變得很是噁心了的,這裏並非忽視原生JS,即便有了一些上層的框架簡化了操做,但核心的邏輯代碼編寫仍然是要寫的,只是關注點不同了的
而在React中,咱們能夠發現,並無操做DOM的過程,一切以數據爲中心,數據是什麼,頁面就顯示什麼
並無像JS,JQ同樣獲取元素,添加事件而後執行一些操做的動做.
對於大型項目迭代開發,這種方式編寫的代碼會更容易的管理,由於React只是用做於視圖UI層的渲染工做,咱們關心的是渲染成什麼樣子,而不須要關心如何實現渲染,怎麼進行DOM操做
這就比如在業界裏有這麼一句話,優秀的程序員關心數據結構,平凡的程序員操心代碼同樣,若是把JQ,與React作這樣一個對比,前者就是React,在這裏沒有任何貶低JQ的意思.
JQ仍然是無比強悍的,每一個技術都有與之對應的應用場景.
何況也沒有JQ實現不了的,只不過是略繁瑣一些而已.
至少在沒有出現React,vue,Angular等這些框架以前,它仍然是霸主統治性地位存在的,然而如今真的不得不說,它的確是在走向落寞.
從上面的React代碼中,咱們能夠歸結出,React的理念能夠用這麼一個公式表示:
UI = render(data)
複製代碼
這個等號左邊UI用戶界面的顯示取決於等號右邊的render函數,這個render函數接收一個數據data做爲參數,這個函數是一個純函數,也能夠稱爲是無狀函數(函數式組件)
換而言之,相似這種只用做UI顯示的函數,咱們能夠用無狀態函數去定義,這在後續若使用了redux作公共數據管理時,把組件裏面的state數據抽離到store當中時,可使用無狀態組件的
由於它只負責頁面的渲染,沒有去作任何邏輯操做的時候,UI組件咱們通常均可以用無狀態組件來定義,UI組件只負責頁面的渲染,固然這並非絕對的,有時候,也能夠作一些簡單邏輯的操做
使用無狀態組件(函數組件),它的性能是高於普通組件的,由於它是函數,而用class類定義的組件,類生成的對象裏面有生命週期函數,因此它執行起來確定沒有函數組件(UI組件)快
對於咱們開發來講,最重要的是區分哪些是屬於data,哪些是屬於render,想要更新用戶界面,要作的是更新data,用戶的界面天然會作出響應,因此把React稱爲響應式編程(面向數據編程)
注意:render函數返回的值,組件生成的 HTML 結構只能有一個單一的根節點
Virtual(虛擬) DOM
元素(JSX)是構成React應用的最小磚塊,它描述了你在在屏幕上看到的UI內容
與瀏覽器的DOM元素不一樣,React元素時建立開銷極小的普通對象,並不會跟原生操做DOM同樣,影響整個DOM的重繪渲染,React DOM會負責更新DOM與React元素保持一致
React只更新它須要更新的部分,React DOM會將元素和它的子元素與它們以前的狀態進行比較,並只會進行必要的更新,例如:以下示例
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
function tick() {
const element = (
<Fragment>
<div style = {{ textAlign: 'center' }}>
<h1>歡迎關注微信itclancoder公衆號</h1>
<p>如今北京時間是 { new Date().toLocaleTimeString() }</p>
</div>
</Fragment>
);
const container = document.getElementById('root');
ReactDOM.render(element, container);
}
setInterval(tick, 1000);
複製代碼
固然,咱們能夠對它進一步的優化,寫成一個組件,以下所示:
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
class Clock extends Component {
constructor(props) {
super(props);
this.state = {
date: new Date()
}
}
render() {
return (
<Fragment>
<div style = {{ textAlign: "center" }}>
<h1>歡迎關注微信itclanCoder公衆號</h1>
<p>如今是北京時間:{ this.state.date.toLocaleTimeString() }</p>
</div>
</Fragment>
);
}
// 生命週期函數,組件掛載時自動執行這個方法,組件已經被渲染到 DOM 中後運行
componentDidMount() {
this.timer = setInterval(() => {
this.tick()
}, 1000)
}
// 組件卸載時,清除定時器
componentWillUnmount(){
clearInterval(this.timer);
}
tick() {
this.setState({
date: new Date()
})
}
}
const container = document.getElementById('root');
ReactDOM.render(<Clock />, container);
複製代碼
對於上面的代碼,涉及到初始化state狀態數據,以及componentDidMount和componentWillUnmount兩個生命週期函數,在組件掛載時設置一個定時器函數,自動更新時間,在組件卸載時,清除定時器,經過setState這個方法,實時更新state數據。更多相關state以及props,生命週期的知識,暫時知道這麼用就能夠了,後續會有更詳細的內容介紹的
儘管每一秒咱們都會新建一個描述整個 UI 樹的元素,可是React DOM 只會更新實際改變了的內容,也就是上面中的文本節點
這是由於React利用Virtual DOM,讓每次渲染都只從新渲染最少的DOM元素
而操做DOM會引發瀏覽器對網頁進行重排重繪。
DOM樹是對HTML的抽象,而vitrtual DOM就是對DOM樹的抽象,虛擬DOM不會觸及瀏覽器,虛擬DOM本質上就是javascript對象,還記得前面說過的JSX是React.createElement()方法的一個語法糖?
它是存在於javascript空間樹形結構,每次自上而下渲染React組件時,會對比這一次產生的virtual DOM和上一次渲染的virtual DOM,對比就會發現差異,而後修改真正的DOM樹時就只須要修改中的部分就能夠了的
React的工做方式及優勢
在沒有組件化React,Vue,Angular以前,毫無疑問,JQ是最直觀易懂的,可是當項目逐漸變得複雜龐大時,用JQ寫出來的代碼耦合度就沒那麼高了的,正是這樣,也就誕生了一些requirejs以及Seajs解決一些問題,可是使用JQ寫出來的代碼每每互相糾纏 以下圖所示
使用React的方式,就能夠避免構建這樣複雜的程序結構,不管何種事件,引起的都是React組件的從新渲染,它只會修改數據變化的的DOM部分,並不須要去關心怎麼去操做DOM
以下圖所示
在React中,對JSX元素上添加事件,是經過on*EventType 這種內聯方式添加的,不須要手動調用瀏覽器原生的 addEventListener 進行事件監聽,在React中,它已經幫咱們封裝好了一些事件類型屬性,當須要給某個元素監聽事件的時候,只須要經過內聯方式,React元素上加on*EventType就能夠了,注意這裏事件類型的寫法,駝峯式命名法
也無需考慮瀏覽器的兼容性,這裏要格外注意的是,這些 on*EventType的事件監聽只能用在普通的 HTML 的標籤上(div,input,p,a等原生瀏覽器支持的標籤),而不能用在組件標籤上。也就是說,<Button onClick={…} />
這樣的寫法是不起做用的
若是想要作到這一點,在組件標籤上監聽事件起做用,也能夠作到,就是結合第三方模塊styled-components樣式組件進行使用,是能夠作到的,更多內容,能夠參考styled-components官方文檔 這裏簡單提一下:
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components';
export const Button = styled.button`
outline: none;
`
// class Button extends Component {
// render() {
// return (
// <button>按鈕</button>
// );
// }
// }
class CountNum extends Component {
constructor(props) {
super(props);
this.state = {
inputVal: 0
}
}
render() {
return (
<div style = {{ textAlign: "center", marginTop: "50px"}}>
<button onClick = { this.handleClickReduce.bind(this) }>-</button>
<input style = {{ textAlign: "center"}} value = { this.state.inputVal } onChange = { this.handleInputChange.bind(this) } />
<button onClick = { this.handleCLickAdd }>+</button>
<Button onClick = { this.handleBtnClick.bind(this) }>按鈕</Button>
</div>
);
}
handleBtnClick() {
alert("我是樣式組件,簡直帥呆了");
}
handleCLickAdd = () => {
this.setState({
inputVal: this.state.inputVal+1
});
}
// handleCLickAdd(){
// this.setState({
// inputVal: this.state.inputVal+1
// })
// }
handleClickReduce() {
this.setState({
inputVal: this.state.inputVal-1
})
}
handleInputChange(e) {
let changeVal = e.target.value;
this.setState({
inputVal: changeVal++
});
}
}
const container = document.getElementById('root');
ReactDOM.render(<CountNum />, container);
複製代碼
具體效果以下所示
React的編程模式是函數式編程來解決用戶界面渲染問題的,也稱爲面向數據編程,一切皆是JS,基於組件開發模式
結語
本文主要從一個簡單的React數字框組件應用開始,分別用原生JS,JQ,React進行了實現,在React中UI視圖取決於render函數返回的內容,數據是什麼,就讓頁面顯示什麼,無需關注DOM操做,而且React引入了虛擬DOM
它是對DOM樹的一種抽象,本質上就是一js對象,當進行視圖的改變時,當React的子元素內容發生改變時,並不會引發整個瀏覽器的重繪和重排,只會更改變化的數據部分,而且在給JSX添加事件監聽時,使用on*EnentType的方式
而且這種事件的監聽,它只做用於原生HTML元素上,若放在自定義的組件上時,是不起做用的,具體解決辦法,能夠引入第三方styled-components模塊的,後續單獨拿一篇幅來講也不爲過的,涉及到的知識仍是挺多的