React基礎
A Simple Component
class HelloWorld extends Component {
render(){
return <div>hello world</div>
}
}
//react-dom render
render(<HelloWorld />, document.getElementById('app'))
複制代碼
JSX
約定使用首字母大小寫來區分本地元件和HTML标簽
裝換createElement
//JSX
var root = <ul className="my-list">
<li>Text Content</li>
</ul>;
React.render(root, document.body);
//Javascript
var child = React.createElement('li', null, 'Text Content');
var root = React.createElement('ul', { className: 'my-list' }, child);
React.render(root, document.body);
複制代碼
JS表達式
//屬性比阿達式
<div className={isDisplay ? "show" : "hide"}></div>
//子節點表達式
<div>
{someFlag ? <span>something</span> : null}
</div>
複制代碼
如果三元操作表達式不夠用,可以通過if語句來決定渲染哪個元件
class Sample extends React.Component {
_decideWitchToRender(){
if (this.props.index > && this.props.someCondition){
return <div>condition1</div>
} else if (this.props.otherCondition) {
return <div>condition2</div>
}
return <div>condition3</div>
}
render() {
return <div>{ this.() }</div>
}
}
複制代碼
屬性擴散
spread operator(...)
var props = {}
props.a = x
props.b = y
var component = <Component {...props} />
複制代碼
HTML實體處理
//Unicode字元
<div>{'First · Second'}</div>
//Unicode編号
<div>{'First \u00b7 Second'}</div>
<div>{'First ' + String.fromCharCode(183) + ' Second'}</div>
//數組裡混合使用字元串
<div>{['First ', <span>·</span>, ' Second']}</div>
//dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{__html: 'First · Second'}} />
複制代碼
自定義HTML屬性
如果往原生 HTML 元素裡傳入 HTML 規範裡不存在的屬性,React 不會顯示它們,data、aria(視聽無障礙)字首除外 特殊情況:自定義元素支援任意屬性
<x-my-component custom-attribute="foo" />
複制代碼
JSX與HTML差異
class -> className, for -> htmlFor, style由css屬性構成的JS對象
...
render(){
const imgStyle = {
transform: "rotate(0)",
WebkitTransform: "rotate(0)"
}
return <div className="rotate" style={imgStyle} ></div>
}
...
複制代碼
React Component
事件系統(合成事件和原生事件) 合成事件會以事件委托(event delegation)的方式綁定到元件最上層,并且在元件解除安裝(unmount)的時候自動銷毀綁定的事件 React合成事件最終是通過委托到document這個DOM節點進行實作,其他節點沒有綁定事件* React合成事件有自己的隊列方式,可以從觸發事件的組建向父元件回溯,可以通過e.stopPropagation來停止合成事件的傳播,但無法阻止原生事件,原生事件可以阻止合成事件 React會管理合成事件的對象建立和銷毀
DOM操作
findDOMNode()
import { findDOMNode } from 'react-dom'
...
componentDidMound() {
const el = findDOMNode(this)
}
...
複制代碼
Refs HTML元素,擷取到DOM元素 自定義元件,擷取到元件執行個體 無狀态元件無法添加ref
...
componentDidMound() {
const el = this.refs.el
}
render() {
return <div ref="el"></div>
}
...
複制代碼
組合元件
const ProfilePic = (props) => {
return (
<img src={'http://graph.facebook.com/' + props.username + '/picture'} />
)
}
const ProfileLink = (props) => {
return (
<a href={'http://www.facebook.com/' + props.username}>
{props.username}
</a>
)
}
const Avatar = (props) => {
return (
<div>
<ProfilePic username={props.username} />
<ProfileLink username={props.username} />
</div>
)
}
複制代碼
父級能通過專門的this.props.children來讀取子級
const Parent = (props) => {
return <div>
<p>something</p>
{props.children}
</div>
}
React.render(<Parent><Avatar username="clark" /></Parent>, document.body)
複制代碼
props.children 通常是一個元件對象的數組,當隻有一個子元素的時候prop.children将是這個唯一子元素
元件生命周期
執行個體化
- getDefaultProps 隻調用一次,所有元件共享
- getInitialState 每個執行個體調用有且隻有一次
- componentWillMount
- render 通過this.props/this.state通路資料、可以傳回null/false/React元件、隻能出現一個頂級元件、不能改變元件的狀态、 不要去修改DOM的輸出
- componentDidMount 真實DOM已被渲染,這時候可以通過ref屬性(this.refs.[refName])擷取真實DOM節點進行操作,如一些事件綁定
存在期
- componentWillReceiveProps 元件的props通過父元件來更改,可以在這裡更新state以觸發render來重新渲染元件
- shouldComponentUpdate 判斷在props/state發生改變後需不需要重新渲染,優化性能的重要途徑
- componentWillUpdate 注意不要在此方法中更新props/state
- render
- componentDidUpdate 類似于componentDidMount
銷毀期
- componentWillUnmount 如在componentDidMount中添加相應的方法與監聽,需在此時銷毀
Mixins
ES6寫法中已經不支援,因為使用mixin場景可以用組合元件方式實作
高階元件
export const ShouldUpdate = (ComposedComponent) => class extends React.Component{
constructor(props) {
super(props)
}
shouldComponentUpdate(nextProps) {
...
}
render() {
return <ComposedComponent {...this.props}/>
}
}
//usage
class MyComponent = class extends Component {
...
}
export default ShouldUpdate(MyComponent)
複制代碼
高階元件的思路是函數式的 每一個擴充就是一個函數
const newComponent = A(B(C(MyComponent)))
複制代碼
實作方式包括: 屬性代理(Props Proxy)和反向繼承(Inheritance Inversion)
//屬性代理
const ppHOC = ComponsedComponent => class extends React.Component {
constructor(props) {
super(props);
this.state = {
newState: ''
};
}
handleSomething = (e) => {
};
render() {
const newProps = Object.assign({}, this.props, this.state);
return <ComponsedComponent ref="compnent" {...this.props} onSomething={this.handleSomething}/>;
}
}
//反向繼承
function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
return supper.render();
}
}
}
複制代碼
注:反向繼承可以做到渲染劫持,通常不建議操作state特别是新增
虛拟DOM Diff
這裡有兩個假設用來降低Diff算法的複雜度O(n^3) -> O(n) 1.兩個相同的元件産生類似的DOM結構,不同元件産生不同的DOM結構 2.對于同一層次的元件子節點,它們可以通過唯一的id進行區分 React的比較僅僅是按照樹的層級分解 React僅僅對同一層的節點嘗試比對,因為實際上,Web中不太可能把一個Component在不同層中移動 不同節點類型比較
//節點類型不同
renderA:<div />
renderB:<span />
=>[removeNode <div />], [insertNode <span />]
//對于React元件的比較同樣适用,React認為不需要話太多時間去比對兩個不大可能有相似之處的component
renderA:<Header />
renderB:<Content />
=>[removeNode <Header />], [insertNode <Content />]
複制代碼
相同類型節點比較
renderA: <div id="before" />
renderB: <div id="after" />
=> [replaceAttribute id "after"]
renderA: <div style={{color: 'red'}} />
renderB: <div style={{fontWeight: 'bold'}} />
=> [removeStyle color], [addStyle font-weight 'bold']
複制代碼
tips:實作自己的元件的時候,保持穩定的DOM結構會有助于性能的提升,例如我們可以通過css隐藏來控制節點顯示,而不是添加/删除
清單節點的比較 當React校正帶有key的子級時,它會被確定它們被重新排序
import React from 'react'
import { render } from 'react-dom'
function createComponent(name) {
class TestNode extends React.Component{
constructor(props) {
super(props)
console.log(name + ' is init')
}
componentDidMount() {
console.log(name + ' did mount')
}
componentWillUnmount() {
console.log(name + ' will unmount')
}
componentDidUpdate() {
console.log(name + ' is updated')
}
render() {
return (
<div className={'node ' + name} data-name={name}>
{this.props.children}
</div>
)
}
}
return TestNode
}
const Root = createComponent('R')
const A = createComponent('A')
const B = createComponent('B')
const C = createComponent('C')
const D = createComponent('D')
class Wrapper extends React.Component{
test1() {
return (<Root>
<A>
<B />
<C />
</A>
</Root>)
}
test2() {
return (<Root>
<A>
<C />
<B />
</A>
</Root>)
}
test3() {
return (<Root>
<A>
<B key="B"/>
<C key="C"/>
</A>
</Root>)
}
test4() {
return (<Root>
<A>
<C key="C"/>
<B key="B"/>
</A>
</Root>)
}
render() {
if (this[this.props.testType]) {
return this[this.props.testType]()
} else {
return <Root />
}
}
}
window.renderTest = function(testType){
render(<Wrapper testType={testType} />, document.getElementById('app'))
}
複制代碼
避免使用state
- componentDidMount、componentDidUpdate、render
- computed data, react components, duplicated data from props
React與AJAX
componentDidMount中發起ajax請求,拿到資料後通過setState方法更新UI 如果異步請求請注意在componentWillUnmount中的abort請求
元件之間互相調用
- 父子元件 React資料流是單向的,父元件資料可以通過設定子元件props進行傳遞,如果想讓子元件改變父元件資料,父元件傳遞一個回調函數給子元件即可(函數傳遞注意事項this.fn.bind(this)和箭頭函數均會傳回新的函數)
- 兄弟元件 将資料挂在在父元件中,由多個子元件共享資料,元件層次太深的問題 -> 全局事件/Context(getChildContext&childContextTypes)
class Parent extends React.Component {
getChildContext() {
return { value: 'parent' };
}
render() {
return <div>
{this.props.children}
</div>
}
}
Parent.childContextTypes = {
value: React.PropTypes.string
}
class Children extends React.Component {
// 如果不需要在構造函數中使用可以不寫,沒有影響
constructor(props, context) {
super(props, context)
console.log(context)
}
render() {
return <div>{'context is: ' + this.context.value}</div>
}
}
//如果要context的内容必須校驗contextTypes
Children.contextTypes = {
value: React.PropTypes.string
}
複制代碼
元件化開發的思考
- 元件盡可能無狀态化(stateless)
- 細粒度的把握,提高複用性
- 配合高階元件實作複雜邏輯
React API unstable_renderSubtreeIntoContainer
Redux
- 整個應用隻有一個store(單一資料源)
- State隻能通過觸發Action來更改
- State每次更改總是傳回一個新的State,即Reducer
Actions
一個包含{type, payload}的對象, type是一個常量标示的動作類型, payload是動作攜帶的資料, 一般我們通過建立函數的方式來生産action,即Action Creator
{
type: 'ADD_ITEM',
name: 'item1'
}
function addItem(id, name) {
return {
type: 'ADD_ITEM',
name,
id
}
}
複制代碼
Reducer
Reducer用來處理Action觸發的對狀态樹的更改 (oldState, action) => newState Redux中隻有一個Store,對應一個State狀态,是以如果把處理都放到一個reducer中,顯示會讓這個函數顯得臃腫和難以維護 将reducer拆分很小的reducer,每個reducer中關注state樹上的特定字段,最後将reducer合并(combineReducers)成root reducer
function items(state = [], action) {
switch (action.type) {
case 'ADD_ITEM'
return [...state, {
action.name,
action.id
}]
default:
return state
}
}
function selectItem(state = '', action) {
switch (action.type) {
case 'SELECT_ITEM'
return action.id
default:
return state
}
}
var rootReducer = combineReducers({
items,
selectItem
})
複制代碼
Store
- 提供State狀态樹
- getState()方法擷取State
- dispatch()方法發送action更改State
- subscribe()方法注冊回調函數監聽State的更改
根據已有的reducer建立store非常容易
import { createStore } from 'redux'
let store = createStore(rootReducer)
複制代碼
資料流
store.dispatch(action) -> reducer(state, action) -> store.getState()
- 1.調用store.dispatch(action)
- 2.redux store調用傳入的reducer函數,根reducer應該把多個reducer輸出合并成一個單一的state樹
- 3.redux store儲存了根reducer傳回的完整的state樹
react-redux
- Provider: 容器元件,用來接收Store,讓Store對子元件可用
- connect: 提供參數如下:
- mapStateToProps 傳回state中挑出部分值合并到props
- mapDispatchToProps 傳回actionCreators合并到props
- mergeProps 自定義需要合并到props的值
- options pure、withRef
Connect本身是一個元件,通過監聽Provider提供的store變化來調用this.setState操作,這裡特别需要注意傳入的mapStateToProps必需隻是你需要的資料,不然作為全局公用的store每次都進行對比顯然不高效也不合理 通過connect()方法包裝好的元件可以得到dispath方法作為元件的props,以及全局state中所需的内容
四個要點
- Redux提供唯一store
- Provider元件包含住最頂層元件,将store作為props傳入
- connect方法将store中資料以及actions通過props傳遞到業務子元件
- 子元件調用action,dispatch到reducer傳回新的state,store同步更新,并通知元件進行更新
展示元件
- 關注UI
- 不依賴action來更新元件
- 通過props接收資料和回調函數改變資料
- 無狀态元件,一般情況下沒有state
容器元件
- 關注運作方式
- 調用action
- 為展示元件提供資料和方法
- 作為資料源,展示元件UI變化控制器
UI與邏輯的分離,利于複用,易于重構
redux中間件
//redux中間件格式
({dispatch, store}) => (next) => (action) => {}
import {createStore, applyMiddleware, combineReducers} from "redux"
let reducer = (store={},action)=>{
return store
}
let logger1 = ({dispatch, getState}) => (next) => (action) => {
console.log("第一個logger開始");
next(action);
}
let logger2 = ({dispatch, getState}) => (next) => (action) => {
console.log("第二個logger開始");
next(action);
}
let store = createStore(reducer,applyMiddleware(logger1,logger2));
store.dispatch({
type: "type1",
})
//輸出
第一個logger開始
第二個logger開始
//通過applyMiddleware包裝後的dispatch
let new_dispatch = (...args) = > logger1(logger2(dispatch(...args)))
複制代碼
immutableJS
替代方案 seamless-immutable
immutable對象的任何修改或者添加删除都會傳回一個新的immutable對象 其實作原理是持久化資料結構(Persistent Data Structure),即舊資料建立新資料時,保證舊資料可用且不變,避免deepcopy把所有節點都複制一遍
兩個 immutable 對象可以使用 === 來比較,這樣是直接比較記憶體位址,性能最好。但即使兩個對象的值是一樣的,也會傳回 false,為了直接比較對象的值,immutable.js 提供了 Immutable.is 來做『值比較』
let map1 = Immutable.Map({a:, b:, c:});
let map2 = Immutable.Map({a:, b:, c:});
map1 === map2; // false
Immutable.is(map1, map2); // true
複制代碼
Immutable.is通過hashCode/valueOf提升比較性能,在react中使用shouldComponentUpdate來進行性能優化的時候能避免deepCopy和deepCompare造成的性能損耗
normalizr
盡可能把state範式化,不存在嵌套
[{
id: ,
title: 'Some Article',
author: {
id: ,
name: 'Dan'
}
}, {
id: ,
title: 'Other Article',
author: {
id: ,
name: 'Dan'
}
}]
//希望的結果
{
result: [, ],
entities: {
articles: {
: {
id: ,
title: 'Some Article',
author:
},
: {
id: ,
title: 'Other Article',
author:
}
},
users: {
: {
id: ,
name: 'Dan'
}
}
}
}
複制代碼