react - JSX
React 背景介紹
React 起源於 Facebook 的內部專案,因為該公司對市場上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套,用來架設 Instagram 的網站。做出來以後,發現這套東西很好用,就在2013年5月開源了。
什麼是React
-
A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES
- 用來構建UI的 JavaScript庫
- React 不是一個 MVC 框架,僅僅是檢視(V)層的庫
- React 官網
- React 中文文件
特點
- 1 使用 JSX語法 建立元件,實現元件化開發,為函式式的 UI 程式設計方式開啟了大門
- 2 效能高的讓人稱讚:通過
diff演算法
和虛擬DOM
實現檢視的高效更新 - 3 HTML僅僅是個開始
> JSX --TO--> EveryThing
- JSX --> HTML
- JSX --> native ios或android中的元件(XML)
- JSX --> VR
- JSX --> 物聯網
為什麼要用React
- 1 使用
元件化
開發方式,符合現代Web開發的趨勢 - 2 技術成熟,社群完善,配件齊全,適用於大型Web專案(生態系統健全)
- 3 由Facebook專門的團隊維護,技術支援可靠
- 4 ReactNative - Learn once, write anywhere: Build mobile apps with React
- 5 使用方式簡單,效能非常高,支援服務端渲染
- 6 React非常火,從技術角度,可以滿足好奇心,提高技術水平;從職業角度,有利於求職和晉升,有利於參與潛力大的專案
React中的核心概念
- 1 虛擬DOM(Virtual DOM)
- 2 Diff演算法(虛擬DOM的加速器,提升React效能的法寶)
虛擬DOM(Vitural DOM)
React將DOM抽象為虛擬DOM,虛擬DOM其實就是用一個物件來描述DOM,通過對比前後兩個物件的差異,最終只把變化的部分重新渲染,提高渲染的效率為什麼用虛擬dom,當dom反生更改時需要遍歷 而原生dom可遍歷屬性多大231個 且大部分與渲染無關 更新頁面代價太大
VituralDOM的處理方式
- 1 用 JavaScript 物件結構表示 DOM 樹的結構,然後用這個樹構建一個真正的 DOM 樹,插到文件當中
- 2 當狀態變更的時候,重新構造一棵新的物件樹。然後用新的樹和舊的樹進行比較,記錄兩棵樹差異
- 3 把2所記錄的差異應用到步驟1所構建的真正的DOM樹上,檢視就更新了
Diff演算法
當你使用React的時候,在某個時間點 render() 函式建立了一棵React元素樹,
在下一個state或者props更新的時候,render() 函式將建立一棵新的React元素樹,
React將對比這兩棵樹的不同之處,計算出如何高效的更新UI(只更新變化的地方)
<!-- 瞭解:
有一些解決將一棵樹轉換為另一棵樹的最小運算元演算法問題的通用方案。然而,樹中元素個數為n,最先進的演算法 的時間複雜度為O(n3) 。
如果直接使用這個演算法,在React中展示1000個元素則需要進行10億次的比較。這操作太過昂貴,相反,React基於兩點假設,實現了一個O(n)演算法,提升效能: -->
-
React中有兩種假定:
- 1 兩個不同型別的元素會產生不同的樹(根元素不同結構樹一定不同)
- 2 開發者可以通過key屬性指定不同樹中沒有發生改變的子元素
Diff演算法的說明 - 1
- 如果兩棵樹的根元素型別不同,React會銷燬舊樹,建立新樹
// 舊樹
<div>
<Counter />
</div>
// 新樹
<span>
<Counter />
</span>
執行過程:destory Counter -> insert Counter
Diff演算法的說明 - 2
- 對於型別相同的React DOM 元素,React會對比兩者的屬性是否相同,只更新不同的屬性
- 當處理完這個DOM節點,React就會遞迴處理子節點。
// 舊
<div className="before" title="stuff" />
// 新
<div className="after" title="stuff" />
只更新:className 屬性
// 舊
<div style={{color: 'red', fontWeight: 'bold'}} />
// 新
<div style={{color: 'green', fontWeight: 'bold'}} />
只更新:color屬性
Diff演算法的說明 - 3
- 1 當在子節點的後面新增一個節點,這時候兩棵樹的轉化工作執行的很好
// 舊
<ul>
<li>first</li>
<li>second</li>
</ul>
// 新
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
執行過程:
React會匹配新舊兩個<li>first</li>,匹配兩個<li>second</li>,然後新增 <li>third</li> tree
- 2 但是如果你在開始位置插入一個元素,那麼問題就來了:
// 舊
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
// 新
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
在沒有key屬性時執行過程:
React將改變每一個子刪除重新建立,而非保持 <li>Duke</li> 和 <li>Villanova</li> 不變
key 屬性
為了解決以上問題,React提供了一個 key 屬性。當子節點帶有key屬性,React會通過key來匹配原始樹和後來的樹。
// 舊
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
// 新
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
執行過程:
現在 React 知道帶有key '2014' 的元素是新的,對於 '2015' 和 '2016' 僅僅移動位置即可
- 說明:key屬性在React內部使用,但不會傳遞給你的元件
- 推薦:在遍歷資料時,推薦在元件中使用 key 屬性:
<li key={item.id}>{item.name}</li>
- 注意:key只需要保持與他的兄弟節點唯一即可,不需要全域性唯一
- 注意:儘可能的減少陣列index作為key,陣列中插入元素的等操作時,會使得效率底下
React的基本使用
- 安裝:
npm i -S react react-dom
-
react
:react 是React庫的入口點 -
react-dom
:提供了針對DOM的方法,比如:把建立的虛擬DOM,渲染到頁面上
// 1. 匯入 react
import React from 'react'
import ReactDOM from 'react-dom'
// 2. 建立 虛擬DOM
// 引數1:元素名稱 引數2:元素屬性物件(null表示無) 引數3:當前元素的子元素string||createElement() 的返回值
const divVD = React.createElement('div', {
title: 'hello react'
}, 'Hello React!!!')
// 3. 渲染
// 引數1:虛擬dom物件 引數2:dom物件表示渲染到哪個元素內 引數3:回撥函式
ReactDOM.render(divVD, document.getElementById('app'))
createElement()的問題
- 說明:
createElement()
方式,程式碼編寫不友好,太複雜
var dv = React.createElement(
"div",
{ className: "shopping-list" },
React.createElement(
"h1",
null,
"Shopping List for "
),
React.createElement(
"ul",
null,
React.createElement(
"li",
null,
"Instagram"
),
React.createElement(
"li",
null,
"WhatsApp"
)
)
)
// 渲染
ReactDOM.render(dv, document.getElementById('app'))
JSX 的基本使用
- 注意:JSX語法,最終會被編譯為 createElement() 方法
- 推薦:使用 JSX 的方式建立元件
- JSX - JavaScript XML
- 安裝:
npm i -D babel-preset-react
(依賴與:babel-core/babel-loader)
注意:JSX的語法需要通過 babel-preset-react 編譯後,才能被解析執行
/* 1 在 .babelrc 開啟babel對 JSX 的轉換 */
{
"presets": [
"env", "react"
]
}
/* 2 webpack.config.js */
module: [
rules: [
{ test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ },
]
]
/* 3 在 js 檔案中 使用 JSX */
const dv = (
<div title="標題" className="cls container">Hello JSX!</div>
)
/* 4 渲染 JSX 到頁面中 */
ReactDOM.render(dv, document.getElementById('app'))
JSX的注意點
-
注意 1: 如果在 JSX 中給元素新增類, 需要使用
className
代替 class- 類似:label 的 for屬性,使用
htmlFor
代替
- 類似:label 的 for屬性,使用
- 注意 2:在 JSX 中可以直接使用 JS程式碼,直接在 JSX 中通過 {} 中間寫 JS程式碼即可
- 注意 3:在 JSX 中只能使用表示式,但是不能出現 語句!!!
- 注意 4:在 JSX 中註釋語法:
{/* 中間是註釋的內容 */}
React元件
React 元件可以讓你把UI分割為獨立、可複用的片段,並將每一片段視為相互獨立的部分。
- 元件是由一個個的HTML元素組成的
- 概念上來講, 元件就像JS中的函式。它們接受使用者輸入(
props
),並且返回一個React物件,用來描述展示在頁面中的內容
React建立元件的兩種方式
- 1 通過 JS函式 建立(無狀態元件)
- 2 通過 class 建立(有狀態元件)
函式式元件 和 class 元件的使用場景說明:
1 如果一個元件僅僅是為了展示資料,那麼此時就可以使用 函式元件
2 如果一個元件中有一定業務邏輯,需要運算元據,那麼就需要使用 class 建立元件,因為,此時需要使用 state
JavaScript函式建立
- 注意:1 函式名稱必須為大寫字母開頭,React通過這個特點來判斷是不是一個元件
- 注意:2 函式必須有返回值,返回值可以是:JSX物件或
null
- 注意:3 返回的JSX,必須有一個根元素
- 注意:4 元件的返回值使用
()
包裹,避免換行問題
function Welcome(props) {
return (
// 此處註釋的寫法
<div className="shopping-list">
{/* 此處 註釋的寫法 必須要{}包裹 */}
<h1>Shopping List for {props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
</ul>
</div>
)
}
ReactDOM.render(
<Welcome name="jack" />,
document.getElementById('app')
)
class建立
在es6中class僅僅是一個語法糖,不是真正的類,本質上還是建構函式+原型 實現繼承
// ES6中class關鍵字的簡單使用
// - **ES6中的所有的程式碼都是執行在嚴格模式中的**
// - 1 它是用來定義類的,是ES6中實現物件導向程式設計的新方式
// - 2 使用`static`關鍵字定義靜態屬性
// - 3 使用`constructor`建構函式,建立例項屬性
// - [參考](http://es6.ruanyifeng.com/#docs/class)
// 語法:
class Person {
// 例項的建構函式 constructor
constructor(age){
// 例項屬性
this.age = age
}
// 在class中定義方法 此處為例項方法 通過例項打點呼叫
sayHello () {
console.log('大家好,我今年' + this.age + '了');
}
// 靜態方法 通過建構函式打點呼叫 Person.doudou()
static doudou () {
console.log('我是小明,我新get了一個技能,會暖床');
}
}
// 新增靜態屬性
Person.staticName = '靜態屬性'
// 例項化物件
const p = new Person(19)
// 實現繼承的方式
class American extends Person {
constructor() {
// 必須呼叫super(), super表示父類的建構函式
super()
this.skin = 'white'
this.eyeColor = 'white'
}
}
// 建立react物件
// 注意:基於 `ES6` 中的class,需要配合 `babel` 將程式碼轉化為瀏覽器識別的ES5語法
// 安裝:`npm i -D babel-preset-env`
// react物件繼承字React.Component
class ShoppingList extends React.Component {
constructor(props) {
super(props)
}
// class建立的元件中 必須有rander方法 且顯示return一個react物件或者null
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
</ul>
</div>
)
}
}
給元件傳遞資料 - 父子元件傳遞資料
- 元件中有一個
只讀的物件
叫做props
,無法給props新增屬性 - 獲取方式:函式引數
props
- 作用:將傳遞給元件的屬性轉化為
props
物件中的屬性
function Welcome(props){
// props ---> { username: 'zs', age: 20 }
return (
<div>
<div>Welcome React</div>
<h3>姓名:{props.username}----年齡是:{props.age}</h3>
</div>
)
}
// 給 Hello元件 傳遞 props:username 和 age(如果你想要傳遞numb型別是資料 就需要向下面這樣)
ReactDOM.reander(<Hello username="zs" age={20}></Hello>, ......)
封裝元件到獨立的檔案中
// 建立Hello2.js元件檔案
// 1. 引入React模組
// 由於 JSX 編譯後會呼叫 React.createElement 方法,所以在你的 JSX 程式碼中必須首先拿到React。
import React from 'react'
// 2. 使用function建構函式建立元件
function Hello2(props){
return (
<div>
<div>這是Hello2元件</div>
<h1>這是大大的H1標籤,我大,我驕傲!!!</h1>
<h6>這是小小的h6標籤,我小,我傲嬌!!!</h6>
</div>
)
}
// 3. 匯出元件
export default Hello2
// app.js中 使用元件:
import Hello2 from './components/Hello2'
props和state
props
- 作用:給元件傳遞資料,一般用在父子元件之間
- 說明:React把傳遞給元件的屬性轉化為一個物件並交給
props
- 特點:
props
是隻讀的,無法給props
新增或修改屬性 -
props.children
:獲取元件的內容,比如:-
<Hello>元件內容</Hello>
中的元件內容
-
// props 是一個包含資料的物件引數,不要試圖修改 props 引數
// 返回值:react元素
function Welcome(props) {
// 返回的 react元素中必須只有一個根元素
return <div>hello, {props.name}</div>
}
class Welcome extends React.Component {
constructor(props) {
super(props)
}
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
state
狀態即資料
- 作用:用來給元件提供
元件內部
使用的資料 - 注意:只有通過
class
建立的元件才具有狀態 - 注意:狀態是私有的,完全由元件來控制
-
注意:不要在
state
中新增render()
方法中不需要的資料,會影響渲染效能!- 可以將元件內部使用但是不渲染在檢視中的內容,直接新增給 this
-
注意:不要在
render()
方法中呼叫 setState() 方法來修改state
的值- 但是可以通過
this.state.name = 'rose'
方式設定state(不推薦!!!!)
- 但是可以通過
// 例:
class Hello extends React.Component {
constructor() {
// es6繼承必須用super呼叫父類的constructor
super()
this.state = {
gender: 'male'
}
}
render() {
return (
<div>性別:{ this.state.gender }</div>
)
}
}
JSX語法轉化過程
// 1、JSX
const element = (
<h1 className="greeting">
Hello, world!
</h1>
)
// 2、JSX -> createElement
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
)
// React elements: 使用物件的形式描述頁面結構
// Note: 這是簡化後的物件結構
const element = {
type: 'h1',
props: {
className: 'greeting',
},
children: ['Hello, world']
}
評論列表案例
- 鞏固有狀態元件和無狀態元件的使用
- 兩個元件:
<CommentList></CommentList>
和<Comment></Comment>
[
{ user: '張三', content: '哈哈,沙發' },
{ user: '張三2', content: '哈哈,板凳' },
{ user: '張三3', content: '哈哈,涼蓆' },
{ user: '張三4', content: '哈哈,磚頭' },
{ user: '張三5', content: '哈哈,樓下山炮' }
]
// 屬性擴充套件
<Comment {...item} key={i}></Comment>
style樣式
// 1. 直接寫行內樣式:
<li style={{border:'1px solid red', fontSize:'12px'}}></li>
// 2. 抽離為物件形式
var styleH3 = {color:'blue'}
var styleObj = {
liStyle:{border:'1px solid red', fontSize:'12px'},
h3Style:{color:'green'}
}
<li style={styleObj.liStyle}>
<h3 style={styleObj.h3Style}>評論內容:{props.content}</h3>
</li>
// 3. 使用樣式表定義樣式:
import '../css/comment.css'
<p className="pUser">評論人:{props.user}</p>
相關文章
- React資料流和元件間的溝通總結
- 單向資料流和雙向繫結各有什麼優缺點?
- 怎麼更好的理解虛擬DOM?
- React中文文件
- React 原始碼剖析系列 - 不可思議的 react diff
- 深入淺出React(四):虛擬DOM Diff演算法解析
元件的生命週期
- 簡單說:一個元件從開始到最後消亡所經歷的各種狀態,就是一個元件的生命週期
元件生命週期函式的定義:從元件被建立,到元件掛載到頁面上執行,再到頁面關閉元件被解除安裝,這三個階段總是伴隨著元件各種各樣的事件,那麼這些事件,統稱為元件的生命週期函式!
- 通過這個函式,能夠讓開發人員的程式碼,參與到元件的生命週期中。也就是說,通過鉤子函式,就可以控制元件的行為
- react component
- React Native 中元件的生命週期
- React 生命週期的管理藝術
- 智慧元件和木偶元件
元件生命週期函式總覽
- 元件的生命週期包含三個階段:建立階段(Mounting)、執行和互動階段(Updating)、解除安裝階段(Unmounting)
- Mounting:
constructor()
componentWillMount()
render()
componentDidMount()
- Updating
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate()
- Unmounting
componentWillUnmount()
元件生命週期 - 建立階段(Mounting)
- 特點:該階段的函式只執行一次
constructor()
- 作用:1 獲取props 2 初始化state
- 說明:通過
constructor()
的引數props
獲取 - 設定state和props
class Greeting extends React.Component {
constructor(props) {
// 獲取 props
super(props)
// 初始化 state
this.state = {
count: props.initCount
}
}
}
// 初始化 props
// 語法:通過靜態屬性 defaultProps 來初始化props
Greeting.defaultProps = {
initCount: 0
};
componentWillMount()
- 說明:元件被掛載到頁面之前呼叫,其在render()之前被呼叫,因此在這方法裡
同步
地設定狀態將不會觸發重渲染 - 注意:無法獲取頁面中的DOM物件
- 注意:可以呼叫
setState()
方法來改變狀態值 - 用途:傳送ajax請求獲取資料
componentWillMount() {
console.warn(document.getElementById('btn')) // null
this.setState({
count: this.state.count + 1
})
}
render()
- 作用:渲染元件到頁面中,無法獲取頁面中的DOM物件
-
注意:不要在render方法中呼叫
setState()
方法,否則會遞迴渲染- 原因說明:狀態改變會重新呼叫
render()
,render()
又重新改變狀態
- 原因說明:狀態改變會重新呼叫
render() {
console.warn(document.getElementById('btn')) // null
return (
<div>
<button id="btn" onClick={this.handleAdd}>打豆豆一次</button>
{
this.state.count === 4
? null
: <CounterChild initCount={this.state.count}></CounterChild>
}
</div>
)
}
componentDidMount()
- 1 元件已經掛載到頁面中
- 2 可以進行DOM操作,比如:獲取到元件內部的DOM物件
- 3 可以傳送請求獲取資料
- 4 可以通過
setState()
修改狀態的值 - 注意:在這裡修改狀態會重新渲染
componentDidMount() {
// 此時,就可以獲取到元件內部的DOM物件
console.warn('componentDidMount', document.getElementById('btn'))
}
元件生命週期 - 執行階段(Updating)
- 特點:該階段的函式執行多次
- 說明:每當元件的
props
或者state
改變的時候,都會觸發執行階段的函式
componentWillReceiveProps()
- 說明:元件接受到新的
props
前觸發這個方法 - 引數:當前元件
props
值 - 可以通過
this.props
獲取到上一次的值 - 使用:若你需要響應屬性的改變,可以通過對比
this.props
和nextProps
並在該方法中使用this.setState()
處理狀態改變 - 注意:修改
state
不會觸發該方法
componentWillReceiveProps(nextProps) {
console.warn('componentWillReceiveProps', nextProps)
}
shouldComponentUpdate()
- 作用:根據這個方法的返回值決定是否重新渲染元件,返回
true
重新渲染,否則不渲染 - 優勢:通過某個條件渲染元件,降低元件渲染頻率,提升元件效能
- 說明:如果返回值為
false
,那麼,後續render()
方法不會被呼叫 - 注意:這個方法必須返回布林值!!!
- 場景:根據隨機數決定是否渲染元件
// - 引數:
// - 第一個引數:最新屬性物件
// - 第二個引數:最新狀態物件
shouldComponentUpdate(nextProps, nextState) {
console.warn('shouldComponentUpdate', nextProps, nextState)
return nextState.count % 2 === 0
}
componentWillUpdate()
- 作用:元件將要更新
- 引數:最新的屬性和狀態物件
- 注意:不能修改狀態 否則會迴圈渲染
componentWillUpdate(nextProps, nextState) {
console.warn('componentWillUpdate', nextProps, nextState)
}
render() 渲染
- 作用:重新渲染元件,與
Mounting
階段的render
是同一個函式 - 注意:這個函式能夠執行多次,只要元件的屬性或狀態改變了,這個方法就會重新執行
componentDidUpdate()
- 作用:元件已經被更新
- 引數:舊的屬性和狀態物件
componentDidUpdate(prevProps, prevState) {
console.warn('componentDidUpdate', prevProps, prevState)
}
元件生命週期 - 解除安裝階段(Unmounting)
- 元件銷燬階段:元件解除安裝期間,函式比較單一,只有一個函式,這個函式也有一個顯著的特點:元件一輩子只能執行依次!
- 使用說明:只要元件不再被渲染到頁面中,那麼這個方法就會被呼叫( 渲染到頁面中 -> 不再渲染到頁面中 )
componentWillUnmount()
-
作用:在解除安裝元件的時候,執行清理工作,比如
- 1 清除定時器
- 2 清除
componentDidMount
建立的DOM物件
React - createClass(不推薦)
React.createClass({})
方式,建立有狀態元件,該方式已經被廢棄!!!- 通過匯入
require('create-react-class')
,可以在不適用ES6的情況下,建立有狀態元件 - getDefaultProps() 和 getInitialState() 方法:是
createReactClass()
方式建立元件中的兩個函式 - React without ES6
- React 不適用ES6
var createReactClass = require('create-react-class');
var Greeting = createReactClass({
// 初始化 props
getDefaultProps: function() {
console.log('getDefaultProps');
return {
title: 'Basic counter!!!'
}
},
// 初始化 state
getInitialState: function() {
console.log('getInitialState');
return {
count: 0
}
},
render: function() {
console.log('render');
return (
<div>
<h1>{this.props.title}</h1>
<div>{this.state.count}</div>
<input type='button' value='+' onClick={this.handleIncrement} />
</div>
);
},
handleIncrement: function() {
var newCount = this.state.count + 1;
this.setState({count: newCount});
},
propTypes: {
title: React.PropTypes.string
}
});
ReactDOM.render(
React.createElement(Greeting),
document.getElementById('app')
);
state和setState
- 注意:使用
setState()
方法修改狀態,狀態改變後,React會重新渲染元件 - 注意:不要直接修改state屬性的值,這樣不會重新渲染元件!!!
- 使用:1 初始化state 2 setState修改state
// 修改state(不推薦使用)
// https://facebook.github.io/react/docs/state-and-lifecycle.html#do-not-modify-state-directly
this.state.test = '這樣方式,不會重新渲染元件';
constructor(props) {
super(props)
// 正確姿勢!!!
// -------------- 初始化 state --------------
this.state = {
count: props.initCount
}
}
componentWillMount() {
// -------------- 修改 state 的值 --------------
// 方式一:
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
}, function(){
// 由於 setState() 是非同步操作,所以,如果想立即獲取修改後的state
// 需要在回撥函式中獲取
// https://doc.react-china.org/docs/react-component.html#setstate
});
// 方式二:
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
}
})
// 或者 - 注意: => 後面需要帶有小括號,因為返回的是一個物件
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}))
}
元件繫結事件
- 1 通過React事件機制
onClick
繫結 -
2 JS原生方式繫結(通過
ref
獲取元素)- 注意:
ref
是React提供的一個特殊屬性 -
ref
的使用說明:react ref
- 注意:
React中的事件機制 - 推薦
- 注意:事件名稱採用駝峰命名法
- 例如:
onClick
用來繫結單擊事件
<input type="button" value="觸發單擊事件"
onClick={this.handleCountAdd}
onMouseEnter={this.handleMouseEnter}
/>
JS原生方式 - 知道即可
- 說明:給元素新增
ref
屬性,然後,獲取元素繫結事件
// JSX
// 將當前DOM的引用賦值給 this.txtInput 屬性
<input ref={ input => this.txtInput = input } type="button" value="我是豆豆" />
componentDidMount() {
// 通過 this.txtInput 屬性獲取元素繫結事件
this.txtInput.addEventListener(() => {
this.setState({
count:this.state.count + 1
})
})
}
事件繫結中的this
- 1 通過
bind
繫結 - 2 通過
箭頭函式
繫結
通過bind繫結
- 原理:
bind
能夠呼叫函式,改變函式內部this的指向,並返回一個新函式 - 說明:
bind
第一個引數為返回函式中this的指向,後面的引數為傳給返回函式的引數
// 自定義方法:
handleBtnClick(arg1, arg2) {
this.setState({
msg: '點選事件修改state的值' + arg1 + arg2
})
}
render() {
return (
<div>
<button onClick={
// 無引數
// this.handleBtnClick.bind(this)
// 有引數
this.handleBtnClick.bind(this, 'abc', [1, 2])
}>事件中this的處理</button>
<h1>{this.state.msg}</h1>
</div>
)
}
- 在建構函式中使用
bind
constructor() {
super()
this.handleBtnClick = this.handleBtnClick.bind(this)
}
// render() 方法中:
<button onClick={ this.handleBtnClick }>事件中this的處理</button>
通過箭頭函式繫結
- 原理:
箭頭函式
中的this由所處的環境決定,自身不繫結this
<input type="button" value="在建構函式中繫結this並傳參" onClick={
() => { this.handleBtnClick('引數1', '引數2') }
} />
handleBtnClick(arg1, arg2) {
this.setState({
msg: '在建構函式中繫結this並傳參' + arg1 + arg2
});
}
受控元件
在HTML當中,像input
,textarea
和select
這類表單元素會維持自身狀態,並根據使用者輸入進行更新。
在React中,可變的狀態通常儲存在元件的state
中,並且只能用setState()
方法進行更新.
React根據初始狀態渲染表單元件,接受使用者後續輸入,改變表單元件內部的狀態。
因此,將那些值由React控制的表單元素稱為:受控元件。
-
受控元件的特點:
- 1 表單元素
- 2 由React通過JSX渲染出來
- 3 由React控制值的改變,也就是說想要改變元素的值,只能通過React提供的方法來修改
- 注意:只能通過setState來設定受控元件的值
// 模擬實現文字框資料的雙向繫結
<input type="text" value={this.state.msg} onChange={this.handleTextChange}/>
// 當文字框內容改變的時候,觸發這個事件,重新給state賦值
handleTextChange = event => {
console.log(event.target.value)
this.setState({
msg: event.target.value
})
}
評論列表案例
[
{name: '小明', content: '沙發!!!'},
{name: '小紅', content: '小明,居然是你'},
{name: '小剛', content: '小明,放學你別走!!!'},
]
props校驗
- 作用:通過型別檢查,提高程式的穩定性
- 命令:
npm i -S prop-types
- 型別校驗文件
- 使用:給類提供一個靜態屬性
propTypes
(物件),來約束props
// 引入模組
import PropTypes from 'prop-types'
// ...以下程式碼是類的靜態屬性:
// propTypes 靜態屬性的名稱是固定的!!!
static propTypes = {
initCount: PropTypes.number, // 規定屬性的型別
initAge: PropTypes.number.isRequired // 規定屬性的型別,且規定為必傳欄位
}
React 單向資料流
- React 中採用單項資料流
- 資料流動方向:自上而下,也就是隻能由父元件傳遞到子元件
- 資料都是由父元件提供的,子元件想要使用資料,都是從父元件中獲取的
- 如果多個元件都要使用某個資料,最好將這部分共享的狀態提升至他們最近的父元件當中進行管理
- 單向資料流
- 狀態提升
react中的單向資料流動:
1 資料應該是從上往下流動的,也就是由父元件將資料傳遞給子元件
2 資料應該是由父元件提供,子元件要使用資料的時候,直接從子元件中獲取
在我們的評論列表案例中:資料是由CommentList元件(父元件)提供的
子元件 CommentItem 負責渲染評論列表,資料是由 父元件提供的
子元件 CommentForm 負責獲取使用者輸入的評論內容,最終也是把使用者名稱和評論內容傳遞給了父元件,由父元件負責處理這些資料( 把資料交給 CommentItem 由這個元件負責渲染 )
元件通訊
- 父 -> 子:
props
- 子 -> 父:父元件通過props傳遞迴調函式給子元件,子元件呼叫函式將資料作為引數傳遞給父元件
- 兄弟元件:因為React是單向資料流,因此需要藉助父元件進行傳遞,通過父元件回撥函式改變兄弟元件的props
- React中的狀態管理: flux(提出狀態管理的思想) -> Redux -> mobx
- Vue中的狀態管理: Vuex
- 簡單來說,就是統一管理了專案中所有的資料,讓資料變的可控
- 元件通訊
Context特性
-
注意:如果不熟悉React中的資料流,不推薦使用這個屬性
- 這是一個實驗性的API,在未來的React版本中可能會被更改
- 作用:跨級傳遞資料(爺爺給孫子傳遞資料),避免向下每層手動地傳遞
props
- 說明:需要配合
PropTypes
型別限制來使用
class Grandfather extends React.Component {
// 型別限制(必須),靜態屬性名稱固定
static childContextTypes = {
color: PropTypes.string.isRequired
}
// 傳遞給孫子元件的資料
getChildContext() {
return {
color: 'red'
}
}
render() {
return (
<Father></Father>
)
}
}
class Child extends React.Component {
// 型別限制,靜態屬性名字固定
static contextTypes = {
color: PropTypes.string
}
render() {
return (
// 從上下文物件中獲取爺爺元件傳遞過來的資料
<h1 style={{ color: this.context.color }}>爺爺告訴文字是紅色的</h1>
)
}
}
class Father extends React.Component {
render() {
return (
<Child></Child>
)
}
}
react-router
- react router 官網
- react router github
- 安裝:
npm i -S react-router-dom
基本概念說明
-
Router
元件本身只是一個容器,真正的路由要通過Route元件
定義
使用步驟
- 1 匯入路由元件
-
2 使用
<Router></Router>
作為根容器,包裹整個應用(JSX)- 在整個應用程式中,只需要使用一次
- 3 使用
<Link to="/movie"></Link>
作為連結地址,並指定to
屬性 - 4 使用
<Route path="/" compoent={Movie}></Route>
展示路由內容
// 1 匯入元件
import {
HashRouter as Router,
Link, Route
} from 'react-router-dom'
// 2 使用 <Router>
<Router>
// 3 設定 Link
<Menu.Item key="1"><Link to="/">首頁</Link></Menu.Item>
<Menu.Item key="2"><Link to="/movie">電影</Link></Menu.Item>
<Menu.Item key="3"><Link to="/about">關於</Link></Menu.Item>
// 4 設定 Route
// exact 表示:絕對匹配(完全匹配,只匹配:/)
<Route exact path="/" component={HomeContainer}></Route>
<Route path="/movie" component={MovieContainer}></Route>
<Route path="/about" component={AboutContainer}></Route>
</Router>
注意點
-
<Router></Router>
:作為整個元件的根元素,是路由容器,只能有一個唯一的子元素 -
<Link></Link>
:類似於vue中的<router-link></router-link>
標籤,to
屬性指定路由地址 -
<Route></Route>
:類似於vue中的<router-view></router-view>
,指定路由內容(元件)展示位置
路由引數
- 配置:通過
Route
中的path屬性來配置路由引數 - 獲取:
this.props.match.params
獲取
// 配置路由引數
<Route path="/movie/:movieType"></Route>
// 獲取路由引數
const type = this.props.match.params.movieType
路由跳轉
- react router - history
-
history.push()
方法用於在JS中實現頁面跳轉 -
history.go(-1)
用來實現頁面的前進(1)和後退(-1)
this.props.history.push('/movie/movieDetail/' + movieId)
fetch
- 作用:Fetch 是一個現代的概念, 等同於 XMLHttpRequest。它提供了許多與XMLHttpRequest相同的功能,但被設計成更具可擴充套件性和高效性。
-
fetch()
方法返回一個Promise
物件
fetch 基本使用
/*
通過fetch請求回來的資料,是一個Promise物件.
呼叫then()方法,通過引數response,獲取到響應物件
呼叫 response.json() 方法,解析伺服器響應資料
再次呼叫then()方法,通過引數data,就獲取到資料了
*/
fetch('/api/movie/' + this.state.movieType)
// response.json() 讀取response物件,並返回一個被解析為JSON格式的promise物件
.then((response) => response.json())
// 通過 data 獲取到資料
.then((data) => {
console.log(data);
this.setState({
movieList: data.subjects,
loaing: false
})
})
跨域獲取資料的三種常用方式
- 1 JSONP
- 2 代理
- 3 CORS
JSONP
- 安裝:
npm i -S fetch-jsonp
- 利用
JSONP
實現跨域獲取資料,只能獲取GET請求 fetch-jsonp
- fetch-jsonp
- 限制:1 只能傳送GET請求 2 需要服務端支援JSONP請求
/* movielist.js */
fetchJsonp('https://api.douban.com/v2/movie/in_theaters')
.then(rep => rep.json())
.then(data => { console.log(data) })
代理
-
webpack-dev-server
代理配置如下: - 問題:webpack-dev-server 是開發期間使用的工具,專案上線了就不再使用 webpack-dev-server
- 解決:專案上線後的程式碼,也是會部署到一個伺服器中,這個伺服器配置了代理功能即可(要求兩個伺服器中配置的代理規則相同)
// webpack-dev-server的配置
devServer: {
// https://webpack.js.org/configuration/dev-server/#devserver-proxy
// https://github.com/chimurai/http-proxy-middleware#http-proxy-options
// http://www.jianshu.com/p/3bdff821f859
proxy: {
// 使用:/api/movie/in_theaters
// 訪問 ‘/api/movie/in_theaters’ ==> 'https://api.douban.com/v2/movie/in_theaters'
'/api': {
// 代理的目標伺服器地址
target: 'https://api.douban.com/v2',
// https請求需要該設定
secure: false,
// 必須設定該項
changeOrigin: true,
// '/api/movie/in_theaters' 路徑重寫為:'/movie/in_theaters'
pathRewrite: {"^/api" : ""}
}
}
}
/* movielist.js */
fetch('/api/movie/in_theaters')
.then(function(data) {
// 將伺服器返回的資料轉化為 json 格式
return data.json()
})
.then(function(rep) {
// 獲取上面格式化後的資料
console.log(rep);
})
CORS - 伺服器端配合
- 示例:NodeJS設定跨域
- 跨域資源共享 CORS 詳解 - 阮一峰
// 通過Express的中介軟體來處理所有請求
app.use('*', function (req, res, next) {
// 設定請求頭為允許跨域
res.header('Access-Control-Allow-Origin', '*');
// 設定伺服器支援的所有頭資訊欄位
res.header('Access-Control-Allow-Headers', 'Content-Type,Content-Length, Authorization,Accept,X-Requested-With');
// 設定伺服器支援的所有跨域請求的方法
res.header('Access-Control-Allow-Methods', 'POST,GET');
// next()方法表示進入下一個路由
next();
});
redux
- 狀態管理工具,用來管理應用中的資料
核心
-
Action:行為的抽象,檢視中的每個使用者互動都是一個action
- 比如:點選按鈕
-
Reducer:行為響應的抽象,也就是:根據action行為,執行相應的邏輯操作,更新state
- 比如:點選按鈕後,新增任務,那麼,新增任務這個邏輯放到 Reducer 中
- 1 建立State
-
Store:
- 1 Redux應用只能有一個store
- 2
getState()
:獲取state - 3
dispatch(action)
:更新state
/* action */
// 在 redux 中,action 就是一個物件
// action 必須提供一個:type屬性,表示當前動作的標識
// 其他的引數:表示這個動作需要用到的一些資料
{ type: 'ADD_TODO', name: '要新增的任務名稱' }
// 這個動作表示要切換任務狀態
{ type: 'TOGGLE_TODO', id: 1 }
/* reducer */
// 第一個引數:表示狀態(資料),我們需要給初始狀態設定預設值
// 第二個引數:表示 action 行為
function todo(state = [], action) {
switch(action.type) {
case 'ADD_TODO':
state.push({ id: Math.random(), name: action.name, completed: false })
return state
case 'TOGGLE_TODO':
for(var i = 0; i < state.length; i++) {
if (state[i].id === action.id) {
state[i].completed = !state[i].completed
break
}
}
return state
default:
return state
}
}
// 要執行 ADD_TODO 這個動作:
dispatch( { type: 'ADD_TODO', name: '要新增的任務名稱' } )
// 內部會呼叫 reducer
todo(undefined, { type: 'ADD_TODO', name: '要新增的任務名稱' })