寫給新人的React快速入門手冊

基礎

組件

React組件大體可分爲三種寫法 一種es6的class語法,繼承React.Component類實現帶有完整生命週期的組件javascript

import React, { Component } from 'react';

export default class SingleComponent extends Component {
  /* 包含一些生命週期函數、內部函數以及變量等 */
  render() {
    return (<div>{/**/}</div>)
  }
}
複製代碼

第二種是無狀態組件,也叫函數式組件html

const SingleComponent = (props) => (
  <div>{props.value}</div>
);
export default SingleComponent;
複製代碼

還有一種較爲特殊,叫高階組件,嚴格來講高階組件只是用來包裝以上兩種組件的一個高階函數java

const HighOrderComponent = (WrappedComponent) => {
  class Hoc extends Component {
    /*包含一些生命週期函數*/
    render() {
      return (<WrappedComponent {...this.props} />); } } return Hoc; } 複製代碼

高階組件的原理是接受一個組件並返回一個包裝後的組件,能夠在返回的組件裏插入一些生命週期函數作相應的操做,高階組件可使被包裝的組件邏輯不受干擾從外部進行一些擴展react

props和state

react中組件自身的狀態叫作state,在es6+的類組件中可使用很簡單的語法進行初始化git

export default class Xxx extends Component {
  state = {
    name: 'sakura',
  }
  render() {
    const { name } = this.state;
    return (<div>{name}</div>);
  }
}
複製代碼

state能夠賦值給某個標籤,若是須要更新state能夠調用this.setState()傳入一個對象,經過這個方法修改state以後綁定了相應值的元素也會觸發渲染,這就是簡單的數據綁定es6

不能經過this.state.name = 'xxx'的方式修改state,這樣就會失去更新state同時相應元素改變的效果github

setState函數是react中較爲重要也是使用頻率較高的一個api,它接受最多兩個參數,第一個參數是要修改的state對象,第二個參數爲一個回調函數,會在state更新操做完成後自動調用,因此setState函數是異步的。 調用this.setState以後react並無馬上更新state,而是將幾回連續調用setState返回的對象合併到一塊兒,以提升性能,如下代碼可能不會產生指望的效果web

class SomeButton extends Component {
  state = {
    value: 1,
  }
  handleClick = () => {
    const { value } = this.state;
    this.setState({ value: value + 1 });
    this.setState({ value: value + 1 });
    this.setState({ value: value + 1 });
    this.setState({ value: value + 1 });
  }
  render() {
    const { value } = this.state;
    return (<div> <span>{vlaue}</span> <button onClick={this.handleClick}>click!</button> </div>);
  }
}
複製代碼

實際上這裏並無對value進行4次+1的操做,react會對這四次更新作一次合併,最終只保留一個結果,相似於redux

Object.assign({},
  { value: value + 1 },
  { value: value + 1 },
  { value: value + 1 },
);
複製代碼

而且由於setState是異步的,因此不能在調用以後立馬獲取新的state,若是要用只能給setState傳入第二個參數回調函數來獲取api

/*省略部分代碼*/
this.setState({
  value: 11,
}, () => {
  const { value } = this.state;
  console.log(value);
})
複製代碼

props是由父元素所傳遞給給子元素的一個屬性對象,用法一般像這樣

class Parent extends Component {
  /*父組件的state中保存了一個value*/
  state = {
    value: 0,
  };

  handleIncrease = () => {
    const { value } = this.state;
    this.setState({ value: value + 1 });
  }

  render() {
    const { value } = this.state;
    // 經過props傳遞給子組件Child,並傳遞了一個函數,用於子組件點擊後修改value
    return (<div> <Child value={value} increase={this.handleIncrease} /> </div>) } } // 子組件經過props獲取value和increase函數 const Child = (props) => ( <div> <p>{props.value}</p> <button onClick={props.increase}>click!</button> </div> ); 複製代碼

props像一個管道,父組件的狀態經過props這個管道流向子組件,這個過程叫作單向數據流

react中修改state和props都會引發組件的從新渲染

組件的生命週期

生命週期是一組用來表示組件從渲染到卸載以及接收新的props以及state聲明的特殊函數

react生命週期函數執行過程
這張圖展現了react幾個生命週期函數執行的過程,能夠簡單把組件的生命週期分爲三個階段,共包含9個生命週期函數,在不一樣階段組件會自動調用

  • 掛載
    • componentWillMount
    • render
    • componentDidMount
  • 更新
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
    • render
    • componentDidUpdate
  • 卸載
    • componentWillUnmount

掛載--componentWillMount

這個階段組件準備開始渲染DOM節點,能夠在這個方法裏作一些請求之類的操做,可是由於組件尚未首次渲染完成,因此並不能拿到任何dom節點

掛載--render

正式渲染,這個方法返回須要渲染的dom節點,而且作數據綁定,這個方法裏不能調用this.setState方法修改state,由於setState會觸發從新渲染,致使再次調用render函數觸發死循環

掛載--componentDidMount

這個階段組件首次渲染已經完成,能夠拿到真實的DOM節點,也能夠在這個方法裏作一些請求操做,或者綁定事件等等

更新--componentWillReceiveProps

當組件收到新的props和state且尚未執行render時會自動觸發這個方法,這個階段能夠拿到新的props和state,某些狀況下可能須要根據舊的props和新的props對比結果作一些相關操做,能夠寫在這個方法裏,好比一個彈窗組件的彈出狀態保存在父組件的state裏經過props傳給自身,判斷這個彈窗彈出能夠這樣寫

class Dialog extends Component {
  componentWillReveiceProps(nextProps) {
    const { dialogOpen } = this.props;
    if (nextProps.dialogOpen && nextProps.dialogOpen !== dialogOpen) {
      /*彈窗彈出*/
    }
  }
}
複製代碼

更新--shouldComponentUpdate

shouldComponentUpdate是一個很是重要的api。react的組件更新過程通過以上幾個階段,到達這個階段須要確認一次組件是否真的須要根據新的狀態再次渲染,確認的依據就是對比新舊狀態是否有所改變,若是沒有改變則返回false,後面的生命週期函數不會執行,若是發生改變則返回true,繼續執行後續生命週期,而react默認就返回true

因此能夠得出shouldComponentUpdate能夠用來優化性能,能夠手動實現shouldComponentUpdate函數來對比先後狀態的差別,從而阻止組件沒必要要的重複渲染

class Demo extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return this.props.value !== nextProps.value;
  }
}
複製代碼

這段代碼是一個最簡單的實現,經過判斷this.props.valuenextProps.value是否相同來決定組件要不要從新渲染,可是實際項目中數據複雜多樣,並不只僅是簡單的基本類型,可能有對象、數組甚至是更深嵌套的對象,而數據嵌套越深就意味着這個方法裏須要作更深層次的對比,這對react性能開銷是極大的,因此官方更推薦使用Immutable.js來代替原生的JavaScript對象和數組

因爲immutablejs自己是不可變的,若是須要修改狀態則返回新的對象,也正由於修改後返回了新對象,因此在shouldComponentUpdate方法裏只須要對比對象的引用就很容易得出結果,並不須要作深層次的對比。可是使用immutablejs則意味着增長學習成本,因此還須要作一些取捨

更新--componentWillUpdate

這個階段是在收到新的狀態而且shouldComponentUpdate肯定組件須要從新渲染而還未渲染以前自動調用的,在這個階段依然能獲取到新的props和state,是組件從新渲染前最後一次更新狀態的機會

更新--render

根據新的狀態從新渲染

更新--componentDidMount

從新渲染完畢

卸載--componentWillmount

組件被卸載以前,在這裏能夠清除定時器以及解除某些事件

組件通訊

不少業務場景中常常會涉及到父=>子組件或者是子=>父組件甚至同級組件間的通訊,父=>子組件通訊很是簡單,經過props傳給子組件就能夠。而子=>父組件通訊則是大多數初學者常常碰到的問題 假設有個需求,子組件是一個下拉選擇菜單,父組件是一個表單,在菜單選擇一項以後須要將值傳給父級表單組件,這是典型的子=>父組件傳值的需求

const list = [
  { name: 'sakura', id: 'x0001' },
  { name: 'misaka', id: 'x0003' },
  { name: 'mikoto', id: 'x0005' },
  { name: 'react', id: 'x0002' },
];

class DropMenu extends Component {
  handleClick = (id) => {
    this.props.handleSelect(id);
  }

  render() {
    <MenuWrap>
      {list.map((v) => (
        <Menu key={v.name} onClick={() => this.handleClick(v.id)}>{v.name}</Menu>
      ))}
    </MenuWrap>
  }
}

class FormLayout extends Component {
  state = {
    selected: '',
  }
  handleMenuSelected = (id) => {
    this.setState({ selected: id });
  }
  render() {
    <div>
      <MenuWrap handleSelect={this.handleMenuSelected} />
    </div>
  }
}
複製代碼

這個例子中,父組件FormLayout將一個函數傳給子組件,子組件的Menu點擊後調用這個函數並把值傳進去,而父組件則收到了這個值,這就是簡單的子=>父組件通訊

而對於更爲複雜的同級甚至相似於叔侄關係的組件能夠經過狀態提高的方式互相通訊,簡單來講就是若是兩個組件互不嵌套,沒有父子關係,這種狀況下,能夠找到他們上層公用的父組件,將state存在這個父組件中,再經過props給兩個組件傳入相應的state以及對應的回調函數便可

路由

React中最經常使用的路由解決方案就是React-router,react-router迄今爲止已經經歷了四個大版本的迭代,每一版api變化較大,本文將按照最新版react-router-v4進行講解

基本用法

使用路由,要先用Router組件將App包起來,並把history對象經過props傳遞進去,最新版本中history被單獨分出一個包,使用的時候須要先引入。對於同級組件路由的切換,須要使用Switch組件將多個Route包起來,每當路由變動,只會渲染匹配到的一個組件

import ReactDOM from 'react-dom';
import createHistory from 'history/createBrowserHistory';
import { Router } from 'react-router';

import App from './App';

const history = createHistory();

ReactDOM.render(
  <Router history={history}> <App /> </Router>,
  element,
);

// App.js
//... 省略部分代碼

import {
  Switch, Route,
} from 'react-router';

class App extends Component {
  render() {
    return (
      <div>
        <Switch>
          <Route exact path="/" component={Dashboard} />
          <Route path="/about" component={About} />
        </Switch>
      </div>
    );
  }
}
複製代碼

CodesanBox在線示例

狀態管理

關於單頁面應用狀態管理能夠先閱讀民工叔這篇文章單頁應用的數據流方案探索

React生態圈的狀態管理方案由facebook提出的flux架構爲基礎,並有多種不一樣實現,而最爲流行的兩種是

flux架構

Flux

Flux is the application architecture that Facebook uses for building client-side web applications. It complements React's composable view components by utilizing a unidirectional data flow. It's more of a pattern rather than a formal framework, and you can start using Flux immediately without a lot of new code.

Flux是facebook用於構建web應用的一種架構,它經過使用單向數據流補充來補充React的組件,它只是一種模式,而不是一個正式的框架

首先,Flux將一個應用分爲三個部分:

  • dispatcher
  • stores
  • views

dispatcher

dispatcher是管理Flux應用中全部數據流的中心樞紐,它的做用僅僅是將actions分發到stores,每個store都監聽本身而且提供一個回調函數,當用戶觸發某個操做時,應用中的全部store都將經過監聽的回調函數來接收這個操做

facebook官方實現的Dispatcher.js

stores

stores包含應用程序的狀態和邏輯,相似於傳統MVC中的model,stores用於存儲應用程序中特定區域範圍的狀態

一個store向dispatcher註冊一個事件並提供一個回調函數,這個回調函數能夠接受action做爲參數,而且基於actionType來區分並解釋操做。在store中提供相應的數據更新函數,在確認更新完畢後廣播一個事件用於應用程序根據新的狀態來更新視圖

// Facebook官方實現FluxReduceStore的用法
import { ReduceStore, Dispatcher } from 'flux';
import Immutable from 'immutable';
const dispatch = new Dispatcher();

class TodoStore extends ReduceStore {
  constructor() {
    super(dispatch);
  }
  getInitialState() {
    return Immutable.OrderedMap();
  }
  reduce(state, action) {
    switch(action.type) {
      case 'ADD_TODO':
        return state.set({
          id: 1000,
          text: action.text,
          complete: false,
        });
      default:
        return state;
    }
  }
}

export default new TodoStore();

複製代碼

views

React提供了views所需的可組合以及能夠自由的從新渲染的視圖,在React最頂層組件裏,經過某種粘合代碼從stores中獲取所需數據,並將數據經過props傳遞到它的子組件中,咱們就能夠經過控制這個頂層組件的狀態來管理頁面任何部分的狀態

Facebook官方實現中有一個FluxContainer.js用於鏈接store與react組件,並在store更新數據後刷新組件狀態更新視圖。基本原理是用一個高階組件傳入Stores和組件須要的state與方法以及組件自己,返回注入了state和action方法的組件,基本用法像這樣

import TodoStore from './TodoStore';
import Container from 'flux';
import TodoActions from './TodoActions';

// 能夠有多個store
const getStores = () => [TodoStore];

const getState = () => ({
  // 狀態
  todos: TodoStore.getState(),

  // action
  onAdd: TodoActions.addTodo,
});

export default Container.createFunctional(App, getStore, getState);
複製代碼

CodeSanbox在線示例 後續會補充flux官方實現的源碼解析

Redux

Redux是由Dan Abramov對Flux架構的另外一種實現,它延續了flux架構中viewsstoredispatch的思想,並在這個基礎上對其進行完善,將本來store中的reduce函數拆分爲reducer,並將多個stores合併爲一個store,使其更利於測試

redux
The Evolution of Flux Frameworks這篇文章,是他對原Flux架構的見解以及他的改進

The first change is to have the action creators return the dispatched action.What looked like this:

export function addTodo(text) {
  AppDispatcher.dispatch({
    type: ActionTypes.ADD_TODO,
    text: text
  });
}
複製代碼

can look like this instead:

export function addTodo(text) {
  return {
    type: ActionTypes.ADD_TODO,
    text: text
  };
}
複製代碼

stores拆分爲單一store和多個reducer

const initialState = { todos: [] };
export default function TodoStore(state = initialState, action) {
  switch (action.type) {
  case ActionTypes.ADD_TODO:
    return { todos: state.todos.concat([action.text]) };
  default:
    return state;
}
複製代碼

Redux把應用分爲四個部分

  • views
  • action
  • reducer
  • store

views能夠觸發一個action,reducer函數內部根據action.type的不一樣來對數據作相應的操做,最後返回一個新的state,store會將全部reducer返回的state組成一個state樹,再經過訂閱的事件函數更新給views

views

react組件做爲應用中的視圖層

action

action是一個簡單的JavaScript對象,包含一個type屬性以及action操做須要用到的參數,推薦使用actionCreator函數來返回一個action,actionCreator函數能夠做爲state傳遞給組件

function singleActionCreator(payload) {
  return {
    type: 'SINGLE_ACTION',
    paylaod,
  };
}
複製代碼

reducer

reducer是一個純函數,簡單的根據指定輸入返回相應的輸出,reducer函數不該該有反作用,而且最終須要返回一個state對象,對於多個reducer,可使用combineReducer函數組合起來

function singleReducer(state = initialState, action) {
  switch(action.type) {
    case 'SINGLE_ACTION':
      return { ...state, value: action.paylaod };
    default:
      return state;
  }
}

function otherReducer(state = initialState, action) {
  switch(action.type) {
    case 'OTHER_ACTION':
      return { ...state, data: action.data };
    default:
      return state;
  }
}

const rootReducer = combineReducer([
  singleReducer,
  otherReducer,
]);

複製代碼

store

redux中store只有一個,經過調用createStore傳入reducer就能夠建立一個store,而且這個store包含幾個方法,分別是subscribe, dispatch,getState,以及replaceReducer,subscribe用於給state的更新註冊一個回調函數,而dispatch用於手動觸發一個action,getState能夠獲取當前的state樹,replaceReducer用於替換reducer,要在react項目中使用redux,必須再結合react-redux

import { connect } from 'react-redux';
const store = createStore(rootReducer);

// App.js
class App extends Component {
  render() {
    return (
      <div> test </div>
    );
  }
}

const mapStateToProps = (state) => ({
  vlaue: state.value,
  data: state.data,
});

const mapDispatchToProps = (dispatch) => ({
  singleAction: () => dispatch(singleActionCreator());
});

export default connect(mapStateToProps, mapDispatchToProps)(App);

// index.js
import { Provider } from 'react-redux';

ReactDOM.render(
  <Provider store={store}> <APP /> </Provider>,
  element,
);
複製代碼

CodeSanbox在線示例

因本人技術水平有限,文中如有錯誤或紕漏歡迎大佬們指出

博客地址,不定時更新

相關文章
相關標籤/搜索