React 基礎 教程 介紹 jsx 語法、Redux

深入一下,這裏import React from 'react'引用的是什麼?

這裏的'react'對應的就是./package.json文件中dependencies中的'react',即在該目錄下用npm install安裝的 react 。npm 安裝的 react 的物理文件是存放在 ./node_modules/react中的,因此引用的東西肯定就在這個文件夾裏面。

打開./node_modules/react/package.json找到"main": "react.js",,這裏的main即指定了入口文件,即./node_modules/react/react.js這個文件。那麼,問題的答案自然就出來了。

jsx 語法

使用一個父節點包裹

jsx 中不能一次性返回零散的多個節點,如果有多個請包涵在一個節點中。

註釋

jsx 中用{/* */}的註釋形式

  return (
      // jsx 外面的註釋
      <div>
          {/* jsx 裏面的註釋 */}
          <p>hello world</p>
      </div>
  )

樣式

對應 html 的兩種形式,jsx 的樣式可以這樣寫:
css樣式:<p className="class1">hello world</p>,注意這裏是className,而 html 中是class
內聯樣式:<p style={{display: 'block', fontSize: '20px'}}>hello world</p>,注意這裏的{{...}},還有fontSize的駝峯式寫法

事件

拿 click 事件爲例,要在標籤上綁定 click 事件,可以這樣寫

class Hello extends React.Component {
  render() {
      return (
        <p onClick={this.clickHandler.bind(this)}>hello world</p>
      )
  }

  clickHandler(e) {
   	// e 即js中的事件對象,例如 e.preventDefault()
    // 函數執行時 this 即組件本身,因爲上面的 .bind(this)
    console.log(Date.now())
  }
}

注意,onClick是駝峯式寫法,以及.bind(this)的作用


循環

在 jsx 中使用循環,一般會用到Array.prototype.map(來自ES5標準)

class Hello extends React.Component {
  render() {
    const arr = ['a', 'b', 'c']
    return (
      <div>
        {arr.map((item, index) => {
             return <p key={index}>this is {item}</p>
         })}
      </div>
    )
  }
}

注意,arr.map是包裹在{}中的,key={index}有助於React的渲染優化,jsx中的{}可放一個可執行的 js 程序或者變量


判斷

jsx中使用判斷一般會用到三元表達式(表達式也是放在{}中的),例如:

return (
  <div>
    <p>段落1</p>
    {
      true 
      ? <p>true</p>
      : <p>false</p>
      </div>
    }
  </div>
)

也可以這樣使用:

<p style={{display: true ? 'block' ? 'none'}}>hello world</p>

數據傳遞 & 數據變化

props

每個頁面都會使用 Header ,但是 Header 上顯示的標題每個頁面肯定是不一樣的。我們需要這樣解決:頁面中引用Header時,這樣寫 <Header title="Hello頁面"/>,即給 Header 組件設置一個 title 屬性。而在 Header 組件中可以這樣取到

 render() {
   return (
     <p>{this.props.title}</p>
   )
 }

在 React 中,父組件給子組件傳遞數據時,就是以上方式,通過給子組件設置 props 的方式,子組件取得 props 中的值即可完成數據傳遞。被傳遞數據的格式可以是任何 js 可識別的數據結構,上面demo是一個字符串。React 中,props 一般只作爲父組件給子組件傳遞數據用,不要試圖去修改自己的 props ,除非你想自找麻煩

state

class Hello extends React.Component {
 constructor(props, context) {
     super(props, context);
     this.state = {
         // 顯示當前時間
         now: Date.now()
     }
 }
 render() {
     return (
         <div>
             <p onClick={this.clickHandler.bind(this)}>hello world {this.state.now}</p>
         </div>
     )
 }
 clickHandler() {
     // 設置 state 的值的時候,一定要用 this.setState ,不能直接賦值修改
     this.setState({
         now: Date.now()
     })
 }
}

智能組件 & 木偶組件

這是用 React 做系統設計時的兩個非常重要的概念。雖然在 React 中,所有的單位都叫做“組件”,但是通過以上例子,我們還是將它們分別放在了./app/containers./app/components兩個文件夾中。爲何要分開呢?

  • 智能組件 在日常開發中,我們也簡稱**“頁面”**。爲何說它“智能”,因爲它只會做一些很聰明的事兒,髒活累活都不幹。它只對數據負責,只需要獲取了數據、定義好數據操作的相關函數,然後將這些數據、函數直接傳遞給具體實現的組件即可。
  • 木偶組件 這裏“木偶”一詞用的特別形象,它總是被人拿線牽着。它從智能組件(或頁面)那裏接受到數據、函數,然後就開始做一些展示工作,它的工作就是把拿到的數據展示給用戶,函數操作開放給用戶。至於數據內容是什麼,函數操作是什麼,它不關心。

生命週期

  • getInitialState

初始化組件 state 數據,但是在 es6 的語法中,我們可以使用以下書寫方式代替

class Hello extends React.Component {
    constructor(props, context) {
        super(props, context);
        // 初始化組件 state 數據
        this.state = {
            now: Date.now()
        }
    }
}
  • render 也是生命週期

最常用的hook,返回組件要渲染的模板。

  • comopentDidMount

組件第一次加載時渲染完成的事件,一般在此獲取網絡數據。實際開始項目開發時,會經常用到。

  • shouldComponentUpdate

主要用於性能優化,React 的性能優化也是一個很重要的話題,後面一併講解。

  • componentDidUpdate

組件更新(包括變量)了之後觸發的事件,一般用於清空並更新數據。實際開始項目開發時,會經常用到。相當於watch

  • componentWillUnmount

組件在銷燬之前觸發的事件,一般用戶存儲一些特殊信息,以及清理setTimeout事件等。

性能檢測

安裝 react 性能檢測工具 npm i react-addons-perf --save,然後在./app/index.jsx

// 性能測試
import Perf from 'react-addons-perf'
if (__DEV__) {
    window.Perf = Perf
}

運行程序。在操作之前先運行Perf.start()開始檢測,然後進行若干操作,運行Perf.stop停止檢測,然後再運行Perf.printWasted()即可打印出浪費性能的組件列表。在項目開發過程中,要經常使用檢測工具來看看性能是否正常。

PureRenderMixin 優化

React 最基本的優化方式是使用PureRenderMixin,安裝工具 npm i react-addons-pure-render-mixin --save,然後在組件中引用並使用

import React from 'react'
import PureRenderMixin from 'react-addons-pure-render-mixin'

class List extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
    }
    //...省略其他內容...
}

React 有一個生命週期 hook 叫做shouldComponentUpdate,組件每次更新之前,都要過一遍這個函數,如果這個函數返回true則更新,如果返回false則不更新。而默認情況下,這個函數會一直返回true,就是說,如果有一些無效的改動觸發了這個函數,也會導致無效的更新

那麼什麼是無效的改動?之前說過,組件中的propsstate一旦變化會導致組件重新更新並渲染,但是如果propsstate沒有變化也莫名其妙的觸發更新了呢(這種情況確實存在)———— 這不就導致了無效渲染嗎?

這裏使用this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);的意思是重寫組件的shouldComponentUpdate函數,在每次更新之前判斷propsstate,如果有變化則返回true,無變化則返回false

因此,我們在開發過程中,在每個 React 組件中都儘量使用PureRenderMixin

react-router 基礎知識

安裝

安裝 react-router npm install react-router --save,完成之後可查看package.json的變化。

創建頁面

創建以下幾個頁面,用於演示

  • ./app/containers/App.jsx 所有頁面的外殼
  • ./app/containers/Home 主頁
  • ./app/containers/List 列表頁
  • ./app/containers/Detail 詳情頁
  • ./app/containers/NotFound 404

注意App.jsx中的代碼目前是這樣子的,而且放在這裏有點多餘,但是在正式的項目開發中,這個文件很有用,而且這個文件和react-router也將會結合的很好。

class App extends React.Component {
    render() {
        return (
            <div>{this.props.children}</div>
        )
    }
}

配置 router

創建 ./app/router/routeMap.jsx 文件,主要代碼如下,詳細的代碼看源文件。

class RouteMap extends React.Component {
    updateHandle() {
        console.log('每次router變化之後都會觸發')
    }
    render() {
        return (
             <Router history={this.props.history} onUpdate={this.updateHandle.bind(this)}>
                <Route path='/' component={App}>
                    <IndexRoute component={Home}/>
                    <Route path='list' component={List}/>
                    <Route path='detail/:id' component={Detail}/>
                    <Route path="*" component={NotFound}/>
                </Route>
            </Router>
        )
    }
}

注意,代碼中path='detail/:id',最後一個標記表示參數,例如/detail/123這個123就是參數,具體的使用在下文詳解。

還要注意,<Route>是可以嵌套的,上面的代碼中只嵌套了一層,在後面的項目開發中,可能會嵌套層次多一些,不過是一個道理

使用 router

./app/index.jsx中的代碼如下,這樣就使用了我們剛纔定義的routeMap組件

import React from 'react'
import { render } from 'react-dom'
import { hashHistory } from 'react-router'

import RouteMap from './router/routeMap'

render(
    <RouteMap history={hashHistory}/>,
    document.getElementById('root')
)

注意這裏的hashHistory,規定用 url 中的 hash 來表示 router 例如localhost:8080/#/list。與之對應的還有一個browserHistory也可用,它就不使用 hash ,直接可以這樣localhost:8080/list表示。但是後者需要服務器端支持,我們這裏用前者。兩者在前端開發中,使用起來都是一樣的,只是表示形式不一樣。

到此爲止就可以npm start運行看效果了。

頁面跳轉

從給一個頁面跳轉到另一個頁面,有兩種方法。第一種是 <Link> 跳轉,例如在 Home 頁面中的代碼。(其實這個<Link>渲染完了就是html中的<a>

import React from 'react'
import { Link } from 'react-router'

class Home extends React.Component {
    render() {
        return (
            <div>
                <p>Home</p>
                <Link to="/list">to list</Link>
            </div>
        )
    }
}

export default Home

另一個方法是使用 js 跳轉,例如在 List 頁面中

import React from 'react'
import { hashHistory } from 'react-router'

class List extends React.Component {
    render() {
        const arr = [1, 2, 3]
        return (
            <ul>
                {arr.map((item, index) => {
                    return <li key={index} onClick={this.clickHandler.bind(this, item)}>js jump to {item}</li>
                })}
            </ul>
        )
    }
    clickHandler(value) {
        hashHistory.push('/detail/' + value)
    }
}

export default List

獲取參數

Detail 頁面需要獲取 url 中的id參數,否則配置這個參數就無用了。可以使用 this.props.params.id 獲取,可查看 Detail 的源碼。


介紹 Redux

如果單純使用 Redux 僅僅安裝 Redux 即可,執行npm install redux --save,不過在 React 中使用 Redux 肯定會用到 react-redux 這一工具,因此這裏一起安裝完,執行npm install react-redux --save

基本使用

可以參見如下代碼

    // 定義計算規則,即 reducer
    function counter(state = 0, action) {
        switch (action.type) {
            case 'INCREMENT':
                return state + 1
            case 'DECREMENT':
                return state - 1
            default:
                return state
        }
    }

    // 根據計算規則生成 store
    let store = createStore(counter)

    // 定義數據(即 state)變化之後的派發規則
    store.subscribe(() => {
        console.log('current state', store.getState())
    })

    // 觸發數據變化
    store.dispatch({type: 'INCREMENT'})
    store.dispatch({type: 'INCREMENT'})
    store.dispatch({type: 'DECREMENT'})

簡單幾十行代碼,就詮釋了 Redux 的設計理念,這裏簡單分析一下:

  • Redux 是一個管理數據的工具,我們創建一個store變量用來管理數據。而這個store不是憑空創建的,創建它的前提是,得設定一個管理規則。以上代碼中,我們的管理規則是:數據(即state)默認是 0,傳入INCREMENT就加一,傳入DECREMENT就減一
  • 創建store用來管理數據,具體的管理形式是什麼呢?第一,要通過一個函數來觸發數據的變化,即dispatch,觸發的時候一定要符合之前定製的規則,否則無效。第二,數據一旦發生變化時,會導致怎樣後果,即subscribe中定義的函數會執行。第三,如何取得當前的數據,即store.getState()。這一塊,熟悉設計模式的同學不難理解,這就是普通的發佈和訂閱的設計模式,也是js種慣用的設計模式。
  • 還有一點特別要注意,即在規則函數中,數據變化時要return一個新的值,而不是直接修改原來的值。這一點和之前提到的Immutable.js一樣,都是使用了不可變數據這一概念。這種設計方式明確了數據的變化時段,使得數據管理更清晰,複雜度更低。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章