一、初入React世界
1.2 JSX語法
class 屬性修改為className
for 屬性修改為 htmFor
展開屬性
使用ES6 rest/spread 特性提高效率
const data = { name: 'foo', value : 'bar' };
// const component = <Component name={data.name} value={data.value} />;
const component = <Component {...data}>
- 自定義HTML 屬性
如果要使用HTML自定義屬性,要使用data- 前綴
<div data-attr="xxx">content</div>
- HTML 轉(zhuǎn)義
dangerouslySetInnerHTML 屬性
<div dangerouslySetInnerHTML={{__html: 'cc ©:2015'}}></div>
1.3 React 組件
- props
- classPrefix: class前綴。對(duì)于組件來說,定義一個(gè)統(tǒng)一的class前綴,對(duì)樣式與交互分離起了非常重要的作用。
- 用funtion prop 與父組件通信
- propTypes
用于規(guī)范 props 的類型與必需的狀態(tài)
1.5 React 生命周期
React生命周期分成兩類:
- 當(dāng)組件在掛載或卸載時(shí)
- 當(dāng)組件接收新的數(shù)據(jù)時(shí),即組件更新時(shí)
推薦初始化組件
import React, { Component, PropTypes } from 'react';
class App extends Component {
// 類型檢查
static propTypes = {
// ...
};
// 默認(rèn)類型
static defaultProps = {
// ...
};
constructor(props) {
super(props);
this.state = {
// ...
};
}
componentWillMount() {
// ...
}
// 在其中使用setState 會(huì)更新組件
componentDidMount() {
// ...
}
render() {
return <div>This is a demo.</div>
}
}
componentWillUnmount 常常會(huì)執(zhí)行一些清理方法
- 數(shù)據(jù)更新過程
如果自身的state更新了,那么會(huì)依次執(zhí)行shouldComponentUpdate、componentWillUpdate 、render 和 componentDidUpdate。
如果組件是由父組件更新 props 而更新的,那么在 shouldComponentUpdate 之前會(huì)先執(zhí)行componentWillReceiveProps 方法。
1.6 React與DOM
1.6.1 ReactDOM
其API非常少,只有findDOMNode,unmountComponentAtNode和render
- findDOMNode
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
componentDidMount() {
// this 為當(dāng)前組件的實(shí)例
const dom = ReactDOM.findDOMNode(this);
}
render() {}
}
findDOMNode只對(duì)已經(jīng)掛載的組件有效
DOM 真正被添加到 HTML 中的生命周期方法是componentDidMount 和 componentDidUpdate 方法
- render
把 React 渲染的Virtual DOM 渲染到瀏覽器的 DOM 當(dāng)中,就要使用 render 方法
React 還提供了一個(gè)很少使用的 unmountComponentAtNode 方法來進(jìn)行
卸載操作。
- refs
refs即reference,組件被調(diào)用時(shí)會(huì)新建一個(gè)該組件的實(shí)例,而refs就會(huì)指向這個(gè)實(shí)例。
二、漫談React
2.1 事件系統(tǒng)
1、事件委派
React沒有把事件直接綁定在真實(shí)的節(jié)點(diǎn)上,而是綁定在最外層,使用一個(gè)統(tǒng)一的事件監(jiān)聽器,這個(gè)事件監(jiān)聽器上維持了一個(gè)映射來保存所有組件內(nèi)部的事件監(jiān)聽和處理函數(shù)。
React中使用DOM原生事件時(shí),要在組件卸載時(shí)手動(dòng)一處,否則很可能出現(xiàn)內(nèi)存泄漏的問題。
2.2 表單
3.select組件
單選和多選兩種。在JSX語法中,可以通過設(shè)置select標(biāo)簽的 multiple={true}
來實(shí)現(xiàn)一個(gè)多選下拉列表。
2.2.2 受控組件
每當(dāng)表單的狀態(tài)發(fā)生變化時(shí),都會(huì)被寫入到組件的state中,這種組件在React中被稱為受控組件(controlled component)。
受控組件更新state的流程:
(1)可以通過在初始 state 中設(shè)置表單的默認(rèn)值
(2)每當(dāng)表單的值發(fā)生變化時(shí),調(diào)用onChange事件處理器
(3)事件處理器通過合成事件對(duì)象e拿到改變后的狀態(tài),并更新state
(4)setState觸發(fā)視圖的重新渲染,完成表單組件值得更新
2.2.3 非受控組件
如果一個(gè)表單組件沒有 value props(單選按鈕和復(fù)選框?qū)?yīng)的是 checked prop)時(shí),就可以稱為非受控組件。相應(yīng)地,也可以使用 defaultValue 和 defaultChecked prop來表示組件的默認(rèn)狀態(tài)。通常,需要通過為其添加ref prop來訪問渲染后的底層DOM元素。
2.3 樣式處理
2.3.3 CSS Modules
CSS Modules 內(nèi)部通過ICSS來解決樣式導(dǎo)入和導(dǎo)出兩個(gè)問題,分別對(duì)應(yīng) :import 和 :export 兩個(gè)新增的偽類
:import("path/to/dep.css") {
localAlias: keyFromDep;
/*...*/
}
:export {
exporteKey: exportedValue;
/*...*/
}
啟用 CSS Modules
// webpack.config.js
css?modules&localIdentName=[name]_[local]-[hash:base64:5]
加上 modules 即為啟用,其中 localIdentName 是設(shè)置生成樣式的命名規(guī)則
使用webpack可以讓全局樣式和CSS Modules的局部樣式和諧共存
module: {
loaders: [{
test: /\.jsx?$/,
loader: 'babel',
}, {
test: /\.scss$/,
exclude: path.resolve(__dirname, 'src/styles'),
loader: 'style!css?modules&localIdentName=[name]_[local]!sass?sourceMap=true',
},{
test: /\.scsss$/,
include: path.resolve(__dirname,'src/styles'),
loader: 'style!css!sass?sourceMap=true',
}]
}
2.4 組件間通信
- 父組件通過props向子組件傳遞需要的信息。
- 子組件向父組件通信
- 利用回調(diào)函數(shù)
- 利用自定義事件機(jī)制
- 跨級(jí)組件通信
React中,我們可以使用 context 來實(shí)現(xiàn)跨級(jí)父子組件間的通信
// listItem組件
class ListItem extends Component {
static contextTypes = {
color: PropTypes.string,
};
render() {
const { value } = this.props;
return (
<li style={{background: this.context.color}}>{value}</li>
)
}
}
// List組件
class List extends Component {
static childContextTypes = {
color: PropTypes.string,
};
getChildContext() {
return {
color: 'red'
}
}
}
父組件中定義了 ChildContext
,這樣從這一層開始的子組件都可以拿到定義的context。
2.5.2 高階組件
實(shí)現(xiàn)高階組件的方法有如下兩種
- 屬性代理(props proxy)。高階組件通過被包裹的React組件來操作 props
- 反向繼承(inheritance inversion)。高階組件繼承于被包裹的React組件
- 屬性代理
import React, { Component } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
render() {
return <WrappedComponent {...this.props}>
}
}
}
當(dāng)然我們也可以用 decorator 來轉(zhuǎn)換
import React, { Component } from 'react';
@MyContainer
class MyComponent extends Component {
render();
}
export default MyComponent;
簡單地替換成作用在類上的decorator,即接受需要裝飾的類為參數(shù)。
- 控制 props
import React, { Component } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
render() {
const newProps = {
text: newText,
};
return <WrappedComponent {...this.props} {...newProps}>
}
}
}
- 通過 refs 使用引用
高階組價(jià)中,我們可以接受 refs 使用 WrappedComponent 的引用。
import React, { Component } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
proc(wrappedComponentInstance) {
wrappedComponentInstance.method();
}
render() {
const props = Object.assign({}, this.props, {
ref: this.proc.bind(this),
});
return <WrappedComponent {...props}>
}
}
}
- 抽象 state
抽象一個(gè)input組件
import React, { Componet } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
constructor(props) {
super(props);
this.state = {
name:'',
}
this.onNameChange = this.onNameChange.bind(this);
}
}
onNameChange(event) {
this.setState({
name: event.target.value,
});
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange,
}
}
return <WrappedComponent {...this.props} {...newProps} />
}
}
- 使用其他元素包裹 WrappedComponent
import React,{ Component } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
render() {
return (
<div style={{display: 'block'}}>
<WrappedComponent {...this.props}>
</div>
)
}
}
}
// 受控input組件使用
@MyContainer
class MyComponent extends Component {
render() {
return <input name="name" {...this.props.name}>
}
}
高階組件和mixin的不同
- 反向繼承
- 渲染劫持
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
if(this.props.loggedIn) {
return super.render();
} else {
return null;
}
}
}
}
// 實(shí)例二:對(duì)render結(jié)果進(jìn)行修改
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
const elementsTree = super.render();
let newProps = {};
if(elementsTree && elementsTree.type === 'input'){
newProps = {value: 'May the force be with you'};
}
const props = Object.assign({}, elementsTree.props, newProps);
const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.childre);
return newElementsTree;
}
}
}
- 控制state
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
return (
<div>
<h2>HOC Debugger Component</h2>
<p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
<p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
{super.render()}
</div>
)
}
}
}
- 組件命名
- 組件參數(shù)
import React, { Component } from 'react';
function HOCFactory(...params) {
return function HOCFactory(WrappedComponent) {
return class HOC extends Component {
render() {
return <WrappedComponent {...this.props}>
}
}
}
}
// 使用
HOCFactoryFactory(params)(WrappedComponent);
// 或者
@HOCFactoryFactory(params);
class WrappedComponent extends React.Component{}
2.6 組件性能優(yōu)化
- 純函數(shù)
- 給定相同的輸入,它總能返回相同的輸出
- 過程沒有副作用
- 沒有額外的狀態(tài)依賴
PureRender
為重新實(shí)現(xiàn)了 shouldComponentUpdate 生命周期方法,讓當(dāng)前傳入的 props
和 state 與之前的作淺比較,如果返回 false,那么組件就不會(huì)執(zhí)行 render 方法。react-addons-perf
量化所做的性能優(yōu)化效果
Perf.start()
Perf.stop()
2.7 動(dòng)畫
TransitionGroup 能幫助我們快捷地識(shí)別出增加或刪除的組件。
React Transition設(shè)計(jì)了以生命周期函數(shù)的方式來實(shí)現(xiàn),即讓子組件的每一個(gè)實(shí)例都實(shí)現(xiàn)相應(yīng)地生命周期函數(shù)。當(dāng)React Transition識(shí)別到某個(gè)子組件增或刪時(shí),則調(diào)用它相應(yīng)地生命周期函數(shù)。我們可以再生命周期函數(shù)中實(shí)現(xiàn)動(dòng)畫邏輯。
如果每一個(gè)子組件的動(dòng)效相同,那么每一個(gè)子組件可以共同用一個(gè)生命周期函數(shù)。因此React Transition 提供了 childFactory 配置,讓用戶自定義一個(gè)封裝子組件的工廠方法,為子組件加上相應(yīng)地生命周期函數(shù)。
React Transition提供的生命周期
- componentWillAppear
- componentDidAppear
- componentWillEnter
- componentDidEnter
- componentWillLeave
- componentDidLeave
componentWillxxx 只要在 componentWillReceiveProps
中對(duì)this.props.children
和nextProps.children
做一個(gè)比較就可以了。componentDidxxx
可以在componentWillxxx
提供一個(gè)回調(diào)函數(shù),用來執(zhí)行componentDidxxx
React CSS Transition 為子組件的每個(gè)生命周期加了不同的className,這樣用戶可以很方便地根據(jù) className 地變化來實(shí)現(xiàn)動(dòng)畫
<ReactCSSTransitionGroup
transitionName="example"
transitionEnterTimeout={400}
>
{items}
</ReactCSSTransitionGroup>
對(duì)應(yīng)地css代碼
.example-enter {
transform: scaleY(0);
&.example-enter-active {
transform: scaleY(1);
transition: transform .4s ease;
}
}
使用react-motion實(shí)現(xiàn)一個(gè)spring開關(guān)
import React, {Component} from ''react;
class Switch extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
open: false,
}
}
handleClick() {
this.setState({
open: !this.state.open
})
}
render() {
return (
<Motion style={{x: spring(this.state.open ? 400 : 0)}}>
{({x}) =>
<div className="demo">
<div
className="demo-block"
onClick={this.handleClick}
style={{
transform: `translate3d(${x}px, 0, 0)`,
}}
/>
</div>
}
</Motion>
)
}
}
深入Redux 應(yīng)用框架
5.1 Redux簡介
5.1.2Redux三大原則
- 單一數(shù)據(jù)源
- 狀態(tài)是只讀地
- 狀態(tài)修改均由純函數(shù)完成
5.1.3 Redux 核心API
Redux核心是一個(gè)store,這個(gè)store是由createStore(reducers[,initialState])方法生成
通過createStore
方法創(chuàng)建的store是一個(gè)對(duì)象,包含4個(gè)方法
- getState(): 獲取store中當(dāng)前的狀態(tài)
- dispatch(action):分發(fā)一個(gè)action,并返回這個(gè)action,這是唯一能改變store中數(shù)據(jù)的方式
- subscribe(listener): 注冊(cè)一個(gè)監(jiān)聽者,它在store發(fā)生改變時(shí)被調(diào)用
- replaceReducer(nextReducer): 更新當(dāng)前store里的reducer,一般只會(huì)在開發(fā)模式中調(diào)用該方法。
5.1.4 與React 綁定
需要使用react-redux
進(jìn)行react和redux的綁定,其提供了一個(gè)組件和API幫助Redux和React進(jìn)行綁定,一個(gè)是 React組件<Provider />,一個(gè)是 connect(),<Provider />接受一個(gè) store 作為props,它是整個(gè)Redux應(yīng)用的頂層組件,而connect()
提供了在整個(gè)React應(yīng)用的任意組件中獲取store中數(shù)據(jù)的功能。
5.2 Redux middleware
Redux 提供了 applyMiddleware 方法來加載 middleware,其源碼如下
import compose from './compose';
export default function applyMiddleware(...middlewares) {
return (next) => (reducer, initialState) => {
// 獲取得到原始的store
let store = next(reducer, initialState);
let dispatch = store.dispatch;
// 賦值一個(gè)空數(shù)組,用來存儲(chǔ)后新的dispatch分裂函數(shù)
let chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware => middleware(middlewareAPI));
// 將分裂的函數(shù)組合每次都會(huì)執(zhí)行,即每次都執(zhí)行這些中間件函數(shù)
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispath
}
}
}
middleware運(yùn)行原理
- 函數(shù)式編程思想設(shè)計(jì)
通過函數(shù)式編程中的 currying。currying 的middleware結(jié)構(gòu)的好處有以下兩點(diǎn)
- 易串聯(lián):不斷currying形成的middleware可以累積參數(shù),再配合組合方式,很容易形成 pipeline來處理數(shù)據(jù)流
- 共享store:applyMiddleware執(zhí)行過程中,store還是舊的,applyMiddleware完成后,所有的middleware內(nèi)部拿到的sotore都是最新且相同的
給middleware分發(fā)store
let newStore = applyMiddleware(mid1, mid2, mid3)(createStore)(reducer, null);
組合串聯(lián)middleware
dispatch = compose(...chain)(store.dispatch)
Redux中compose的實(shí)現(xiàn)
function compose(...funs) {
return arg => funs.reduceRight((composed, f) => f((composed), arg))
}
compose(...funcs) 返回的是一個(gè)匿名函數(shù),其中 funcs 就是 chain 數(shù)組。當(dāng)調(diào)用 reduceRight
時(shí),依次從 funcs 數(shù)組的右端取一個(gè)函數(shù) fx 拿來執(zhí)行,fx 的參數(shù) composed 就是前一次 fx+1 執(zhí)
行的結(jié)果,而第一次執(zhí)行的 fn(n 代表 chain 的長度)的參數(shù) arg 就是 store.dispatch。
- 在 middleware 中調(diào)用 dispatch 會(huì)發(fā)生什么
如果這個(gè)middleware粗暴的調(diào)用 store.dispatch(acton),就會(huì)形成無線循環(huán)了。
這里我們就用到了Redux Thunk。
Redux Thunk 會(huì)判斷 action 是否是函數(shù)。如果是,則執(zhí)行 action,否則繼續(xù)傳遞 action 到下一個(gè) middleware。
const tuhun = store => next => action => {
typeof action === 'function' ?
action(store.dispatch, store.getState) :
next(action)
}
5.3 Redux 異步流
5.3.1 使用 middleware 簡化異步請(qǐng)求
- redux-thunk
我們?cè)賮砜纯?redux-thunk 的源代碼:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
模擬請(qǐng)求天氣的異步請(qǐng)求,action的寫法
function getWeather(url, params) {
return (dispatch, action) {
fetch(url, params)
.then(result => {
dispatch({
type: 'GET_WEATHER_SUCCESS',
payload: result,
})
})
.catch(err => {
dispatch({
type: 'GET_WEATHER_ERROR',
payload: err,
})
})
}
}
- redux-promise
import { isFSA } from 'flux-standard-action';
function isPromise(val) {
return val && typeof val.then === 'function';
}
export default function promiseMiddleware({ dispatch }) {
return next => action => {
if (!isFSA(action)) {
return isPromise(action)
? action.then(dispatch)
: next(action);
}
return isPromise(action.payload)
? action.payload.then(
result => dispatch({ ...action, payload: result }),
error => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
}
)
: next(action);
};
}
我們利用 ES7 的 async 和 await 語法,可以簡化上述異步過程:
const fetchData = (url, params) => fetch(url, params);
async function getWeather(url, params) {
const result = await fetchData(url, params);
if (result.error) {
220 第 5 章 深入 Redux 應(yīng)用架構(gòu)
return {
type: 'GET_WEATHER_ERROR',
error: result.error,
};
}
return {
type: 'GET_WEATHER_SUCCESS',
payload: result,
};
}
- redux-saga
在 Redux 社區(qū),還有一個(gè)處理異步流的后起之秀,名為 redux-saga。它與上述方法最直觀的
不同就是用 generator 替代了 promise,我們通過 Babel 可以很方便地支持 generator.
Redux 與 路由
我們可以通過 <Router> 、<Route> 這兩個(gè)標(biāo)簽以及一系列屬性
定義整個(gè) React 應(yīng)用的路由方案。
前端開發(fā)熱加載,安裝 webpack-dev-server
npm install -D webpack-dev-server
./node_modules/.bin/webpack-dev-server --hot --inline --content-base
在 mapStateToProps 中,我們從整棵 Redux 狀態(tài)樹中選取了 state.home.list 分支作為當(dāng)前
組件的 props,并將其命名為 list。這樣,在 Home 組件中,就可以使用 this.props.list 來獲取
到所有 PreviewListRedux 中定義的狀態(tài)。
而在 mapDispatchToProps 中,我們從前面提到的 HomeRedux.js 中引入了 listActions,并使
用 Redux 提供的工具函數(shù)將 listActions 中的每一個(gè) action creator(目前只有一個(gè))與 dispatch 進(jìn)
行綁定,最終我們可以在 Home 組件中使用 this.props.listActions 來獲取到綁定之后的 action
creator。