摘要: 很多值得了解的細節。
- 原文:React入門看這篇就夠了
- 作者:Random
Fundebug經授權轉載,版權歸原作者所有。
React 背景介紹
React 起源於 Facebook 的內部項目,因為該公司對市場上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套,用來架設 Instagram 的網站。做出來以后,發現這套東西很好用,就在2013年5月開源了。
什么是React
- A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES
- 用來構建UI的 JavaScript庫
- React 不是一個 MVC 框架,僅僅是視圖(V)層的庫
- React 官網
- React 中文文檔
特點
- 使用 JSX語法 創建組件,實現組件化開發,為函數式的 UI 編程方式打開了大門
- 性能高的讓人稱贊:通過
diff算法
和虛擬DOM
實現視圖的高效更新 - HTML僅僅是個開始
> JSX --TO--> EveryThing
- JSX --> HTML
- JSX --> native ios或android中的組件(XML)
- JSX --> VR
- JSX --> 物聯網
為什么要用React
- 使用
組件化
開發方式,符合現代Web開發的趨勢 - 技術成熟,社區完善,配件齊全,適用於大型Web項目(生態系統健全)
- 由Facebook專門的團隊維護,技術支持可靠
- ReactNative - Learn once, write anywhere: Build mobile apps with React
- 使用方式簡單,性能非常高,支持服務端渲染
- React非常火,從技術角度,可以滿足好奇心,提高技術水平;從職業角度,有利於求職和晉升,有利於參與潛力大的項目
React中的核心概念
- 虛擬DOM(Virtual DOM)
- Diff算法(虛擬DOM的加速器,提升React性能的法寶)
虛擬DOM(Vitural DOM)
React將DOM抽象為虛擬DOM,虛擬DOM其實就是用一個對象來描述DOM,通過對比前后兩個對象的差異,最終只把變化的部分重新渲染,提高渲染的效率
為什么用虛擬dom,當dom反生更改時需要遍歷 而原生dom可遍歷屬性多大231個 且大部分與渲染無關 更新頁面代價太大
VituralDOM的處理方式
- 用 JavaScript 對象結構表示 DOM 樹的結構,然后用這個樹構建一個真正的 DOM 樹,插到文檔當中
- 當狀態變更的時候,重新構造一棵新的對象樹。然后用新的樹和舊的樹進行比較,記錄兩棵樹差異
- 把2所記錄的差異應用到步驟1所構建的真正的DOM樹上,視圖就更新了
Diff算法
當你使用React的時候,在某個時間點 render() 函數創建了一棵React元素樹,
在下一個state或者props更新的時候,render() 函數將創建一棵新的React元素樹,
React將對比這兩棵樹的不同之處,計算出如何高效的更新UI(只更新變化的地方)
有一些解決將一棵樹轉換為另一棵樹的最小操作數算法問題的通用方案。然而,樹中元素個數為n,最先進的算法 的時間復雜度為O(n3) 。
如果直接使用這個算法,在React中展示1000個元素則需要進行10億次的比較。這操作太過昂貴,相反,React基於兩點假設,實現了一個O(n)算法,提升性能:
React中有兩種假定:
- 兩個不同類型的元素會產生不同的樹(根元素不同結構樹一定不同)
- 開發者可以通過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的注意點
- 如果在 JSX 中給元素添加類, 需要使用
className
代替 class- 類似:label 的 for屬性,使用
htmlFor
代替
- 類似:label 的 for屬性,使用
- 在 JSX 中可以直接使用 JS代碼,直接在 JSX 中通過 {} 中間寫 JS代碼即可
- 在 JSX 中只能使用表達式,但是不能出現 語句!!!
- 在 JSX 中注釋語法:
{/* 中間是注釋的內容 */}
React組件
React 組件可以讓你把UI分割為獨立、可復用的片段,並將每一片段視為相互獨立的部分。
- 組件是由一個個的HTML元素組成的
- 概念上來講, 組件就像JS中的函數。它們接受用戶輸入(
props
),並且返回一個React對象,用來描述展示在頁面中的內容
React創建組件的兩種方式
- 通過 JS函數 創建(無狀態組件)
- 通過 class 創建(有狀態組件)
函數式組件 和 class 組件的使用場景說明:
- 如果一個組件僅僅是為了展示數據,那么此時就可以使用 函數組件
- 如果一個組件中有一定業務邏輯,需要操作數據,那么就需要使用 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>
)
}
}
推薦大家使用Fundebug,一款很好用的BUG監控工具~
給組件傳遞數據 - 父子組件傳遞數據
- 組件中有一個
只讀的對象
叫做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 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()
- 組件已經掛載到頁面中
- 可以進行DOM操作,比如:獲取到組件內部的DOM對象
- 可以發送請求獲取數據
- 可以通過
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()
- 作用:在卸載組件的時候,執行清理工作,比如
- 清除定時器
- 清除
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
}))
}
推薦大家使用Fundebug,一款很好用的BUG監控工具~
組件綁定事件
- 通過React事件機制
onClick
綁定 - 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
- 通過
bind
綁定 - 通過
箭頭函數
綁定
通過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
})
}
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中的單向數據流動:
- 數據應該是從上往下流動的,也就是由父組件將數據傳遞給子組件
- 數據應該是由父組件提供,子組件要使用數據的時候,直接從子組件中獲取
在我們的評論列表案例中:
- 數據是由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
})
})
推薦大家使用Fundebug,一款很好用的BUG監控工具~
跨域獲取數據的三種常用方式
- JSONP
- 代理
- 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 中
- 創建State
- Store:
- Redux應用只能有一個store
getState()
:獲取statedispatch(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: '要添加的任務名稱' })
推薦大家使用Fundebug,一款很好用的BUG監控工具~