深入一下,這裏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
,就是說,如果有一些無效的改動觸發了這個函數,也會導致無效的更新
那麼什麼是無效的改動?之前說過,組件中的props
和state
一旦變化會導致組件重新更新並渲染,但是如果props
和state
沒有變化也莫名其妙的觸發更新了呢(這種情況確實存在)———— 這不就導致了無效渲染嗎?
這裏使用this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
的意思是重寫組件的shouldComponentUpdate
函數,在每次更新之前判斷props
和state
,如果有變化則返回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
一樣,都是使用了不可變數據這一概念。這種設計方式明確了數據的變化時段,使得數據管理更清晰,複雜度更低。