React入門看這篇就夠了

摘要: 很多值得了解的細節。

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的處理方式

  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中有兩種假定:

  • 兩個不同類型的元素會產生不同的樹(根元素不同結構樹一定不同)
  • 開發者可以通過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代替
  • 在 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>

組件的生命週期

  • 簡單說:一個組件從開始到最後消亡所經歷的各種狀態,就是一個組件的生命週期

組件生命週期函數的定義:從組件被創建,到組件掛載到頁面上運行,再到頁面關閉組件被卸載,這三個階段總是伴隨着組件各種各樣的事件,那麼這些事件,統稱爲組件的生命週期函數!

組件生命週期函數總覽

  • 組件的生命週期包含三個階段:創建階段(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.propsnextProps並在該方法中使用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,textareaselect這類表單元素會維持自身狀態,並根據用戶輸入進行更新。
在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

基本概念說明

  • 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 - 服務器端配合

// 通過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():獲取state
    • 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: '要添加的任務名稱' })

推薦大家使用Fundebug,一款很好用的BUG監控工具~

相關文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章