React.js 新手快速入門 - 開山篇

著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

在閱讀以前,會有一些小夥伴以爲疑惑,做者怎麼寫前端文章了呢,做者不是專一後端Java嗎?這是怎麼了?其實否則,在4年前,那個時候尚未流行vue和react,身爲後端程序員的咱們,無論是java仍是php程序員,都是須要寫前端的,不過那個時候的前端沒有如今那麼多東西。咱們通常叫美工畫好靜態頁面,而後交給後端程序員,後端程序員在靜態頁面中加入js代碼,或者把靜態頁面替換成jsp、velocity等靜態模板語言的代碼,一個動態效果的頁面就完成了。隨着互聯網的不斷髮展,程序員工種的劃分也愈來愈明細,如今的前端和做者曾經那個時候已經大不同了,不管是思想仍是語言風格,爲了學習下如何本身製做頁面,也爲了感覺下前端代碼的魅力,故選擇了React.js 前端框架做爲學習目標。其實前端頗有趣,有意思!身爲後端程序員的你不打算了解一下嘛~php

準備工做

在學習 react 以前,咱們須要先安裝對應的運行環境,工欲善其事必先利其器。首先安裝好以下環境:css

不知道個人讀者是否是徹底不懂前端,建議讀者有一點點的 Html、Css、java script、es6 的基礎,實在沒有建議花個1~2天學習下。html

熟悉官方create-react-app腳手架

react 前端項目和咱們平時的java項目同樣,都有其本身的項目結構,java的項目結構有IDE開發工具幫咱們生產,在本文中,咱們使用facebook 的 create-react-app 腳手架項目來幫咱們生成 react 項目結構,操做以下:前端

# 全局安裝官方腳手架
npm i -g create-react-app 
# 初始化建立一個基於 react 的項目
create-react-app 01_jpview_class
# 設置 npm 下載鏡像源爲淘寶, 和設置 maven 倉庫源一個意思
npm config set registry http://registry.npm.taobao.org
複製代碼

這個時候就開始建立項目了,時間有點長,由於正在下載須要的插件和依賴包,完成後項目結構以下:

├── README.md        文檔
├── package-lock.json
├── package.json      npm 依賴
├── public      靜態資源
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src      源碼
   ├── App.css
   ├── App.js      根組件
   ├── App.test.js      測試
   ├── index.css      全局樣式
   ├── index.js      入口
   ├── logo.svg
   └── serviceWorker.js      pwa支持
複製代碼

什麼是JSX語法

刪除src目錄下的因此文件,新建一個 index.js文件,內容爲vue

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App/>, document.querySelector('#root'))
複製代碼

新建 App.js文件,內容爲:java

import React, { Component } from "react";

export default class App extends Component{
    render(){
        return <div>
            <button>雷猴啊</button>
        </div>
    }
}
export default KaikebaCart
複製代碼

上面的代碼看起來會有感到困惑的地方,首先就是ReactDOM.render(<App />, document.querySelector('#root')); 看起來是js和html的混合體,這種語法被稱之爲JSX,其實際核心的邏輯徹底是js來實現的。node

在項目目錄終端執行如下命令能夠看到效果react

# 下載依賴包
npm install
# 啓動運行項目
npm start
複製代碼

學習react基礎語法

如下全部代碼均可以直接複製到 index.js文件中 體驗效果git

React 組件

React 的世界裏一切皆是組件,咱們使用class語法構建一個最基本的組件,組件的使用方式和HTML相同,組件的render函數返回渲染的一個JSX頁面,而後使用ReactDom渲染到頁面裏程序員

import React from 'react';
import ReactDOM from 'react-dom';
// 繼承React.Component表示App是一個組件
class App extends React.Component {
    render() {
      return <div> Hello React </div>
    }
  }
 // ReactDOM.render()方法把 App中的內容追加到 index.html 中 <div id="root">的標籤上
ReactDOM.render(<App/>, document.querySelector('#root'))
複製代碼

屬性傳遞

React組件使用和html相似的方式傳遞參數,在組件內部,使用this.props獲取全部的傳遞的參數,在JSX裏使用變量,使用{}包裹

import React from 'react';
import ReactDOM from 'react-dom';
// 繼承React.Component表示App是一個組件
class App extends React.Component {
    render() {
      // 獲取<App name="React"> 傳遞過來的屬性name值
      return <div> Hello {this.props.name} </div>
    }
  }
 // ReactDOM.render()方法把 App中的內容追加到 index.html 中 <div id="root">的標籤上
ReactDOM.render(<App name="React" />, document.querySelector('#root'))
複製代碼

JSX

JSX是一種js的語法擴展,表面上像HTML,本質上仍是經過babel轉換爲js執行,全部在JSX裏可使用{}來寫js的語法,JSX本質上就是轉換爲React.createElement在React內部構建虛擬Dom,最終渲染出頁面

import React from 'react';
import ReactDOM from 'react-dom';
// 繼承React.Component表示App是一個組件
class App extends React.Component {
    render() {
      return (
        <div>
          // {2+2} js的計算語法,結果爲4
          Hello {this.props.name}, I am {2 + 2} years old
        </div>
      )
    }
  }
 // ReactDOM.render()方法把 App中的內容追加到 index.html 中 <div id="root">的標籤上
ReactDOM.render(<App name="React" />, document.querySelector('#root'))
複製代碼

State和事件綁定

咱們到如今爲止尚未更新過UI頁面,React內部經過this.state變量來維護內部的狀態,而且經過this.stateState來修改狀態,render裏用到的state變量,也會自動渲染到UI,咱們如今constructor()來初始化state,在JSX語法裏使用this.state.num獲取,而後jsx裏使用onClick綁定點擊事件,注意這裏須要在constructor()裏使用bind()方法綁定this指向,而後內部調用this.setState修改值,注意這裏不能寫成this.state.num+1,而是要調用this.setState,設置並返回一個全新的num值。

import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component {
    constructor(props){
      super(props)
      // 初始化構造是設置內部狀態 num值爲 1 
      this.state = {
        num:1
      }
      // 把handleClick()方法綁定到當前對象Counter上
      this.handleClick = this.handleClick.bind(this)
    }
    handleClick(){
      // 改變內部狀態 num 的值
      this.setState({
        num:this.state.num + 1
      })
    }
    render() {
      return (
        <div>
          <p>{this.state.num}</p>
            {/*{this.handleClick} js語法調用當前對象的handleClick()方法*/}
          <button onClick={this.handleClick}>click</button>
        </div>
      )
    }
  }

ReactDOM.render(<Counter/>, document.querySelector('#root'))
複製代碼

生命週期

在組件內部存在一些特殊的方法,會在組件的不一樣階段執行,好比組件加載完畢後會執行componentDidMount函數,組件更新的時候,會執行shouldComponentUpdate函數,若是返回true的話,就會一次執行componentWillMountrendercomponentDidMount,若是返回false的話,就不會執行。

import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component {
    constructor(props){
      super(props)
      this.state = {
        num:1
      }
      this.handleClick = this.handleClick.bind(this)
    }
    // 生命方法--組件渲染完成,只執行一次
    componentDidMount(){
      console.log('componentDidMount 函數觸發')
    }
    // 生命方法--避免組件重複或者無心義渲染
    shouldComponentUpdate(nextProps,nextState){
      if (nextState.num%2) {
        return true
      }
      return false
    }
    handleClick(){
      this.setState({
        num:this.state.num+1
      })
    }
    render() {
      return (
        <div>
          <p>{this.state.num}</p>
          <button onClick={this.handleClick}>click</button>
        </div>
      )
    }
  }

ReactDOM.render(<Counter/>, document.querySelector('#root'))
複製代碼

生命週期流程圖:

表單

用戶想提交數據到後臺,表單元素是最經常使用的,一個常見的表單由forminputlabel等標籤構成,咱們經過onChange()方法控制value的值,最終經過state,讓在的html input中輸入內容和`React``關聯起來。

import React from 'react';
import ReactDOM from 'react-dom';

class TodoList extends React.Component {
    constructor(props){
      super(props)
      this.state = {
        text:''
      }
      this.handleClick = this.handleClick.bind(this)
      this.handleChange = this.handleChange.bind(this)
    }
    handleClick(){
      // 若是內部狀態 text有值,則把值清空''
      if (this.state.text) {
        this.setState({
          text:''
        })
      }
    }
    handleChange(e){
      // 獲取事件元素input的值賦值給內部狀態 text 中
      this.setState({
        text:e.target.value
      })
    }
    render() {
      return (
        <div>
          {/* 顯示內部狀態 text 的內容*/}
          {this.state.text}
          {/*input接收到輸入值調用handleChange()方法*/}
          <input type="text" value={this.state.text} onChange={this.handleChange}/>
          {/*點擊按鈕調用handleClick()方法*/}
          <button onClick={this.handleClick}>clear</button>    
        </div>
      )
    }
  }
  
ReactDOM.render(<TodoList/>, document.querySelector('#root'))
複製代碼

渲染列表

頁面裏序列化的數據,好比用戶列表,都是一個數組,咱們經過map函數把數組直接映射爲JSX,可是咱們直接渲染列表,打開console的時候會看到Each child in an array or iterator should have a unique "key" prop.報錯。在渲染列表的時候,咱們須要每一個元素都有一個惟一的key屬性,這樣React在數據變化的時候,知道哪些dom應該發生變化 尤爲注意key要惟一,建議每一個字段惟一id,或者使用索引

import React from 'react';
import ReactDOM from 'react-dom';

class TodoList extends React.Component {
    constructor(props){
      super(props)
      // 內部裝填屬性初始化值
      this.state = {
        todos:['Learn React','Learn Ant-design','Learn Koa'],
        text:''
      }
      this.handleClick = this.handleClick.bind(this)
      this.handleChange = this.handleChange.bind(this)
    }
    handleClick(){
      if (this.state.text) {
        this.setState(state=>({
           // 若是內部狀態 text有值,追加到解構的todos數組後
          todos:[...state.todos,state.text],
           // 若是內部狀態 text有值,則把值清空''
          text:''
        }))
      }
  
    }
    handleChange(e){
      // 獲取事件元素input的值賦值給內部狀態 text 中
      this.setState({
        text:e.target.value
      })
    }
    render() {
      return (
        <div>
          {/*input接收到輸入值調用handleChange()方法*/}
          <input type="text" value={this.state.text} onChange={this.handleChange}/>
          {/*點擊按鈕調用handleClick()方法*/}
          <button onClick={this.handleClick}>add</button>
          <ul>
            {/*map()循環輸出JSX內容給ReactDOM*/}
            {this.state.todos.map(v=>{
              return <li key={v}>{v}</li>
            })}
          </ul>
        </div>
      )
    }
  }

ReactDOM.render(<TodoList/>, document.querySelector('#root'))
複製代碼

React16新增了什麼

2017年9月27日,Facebook 官方發佈了 React 16.0。相較於以前的 15.x 版本,v16是第一個核心模塊重寫了的版本,而且在異常處理,核心架構和服務端渲染方面都有更新。

  • render 函數支持返回數組和字符串
  • 異常處理,添加componentDidCatch鉤子獲取組件錯誤
  • 新的組件類型 portals 能夠渲染當前容器dom以外的節點
  • 打包的文件體積減小 30%
  • 更換開源協議爲MIT許可
  • Fiber架構,支持異步渲染
  • 更好的服務端渲染,支持字節流渲染
import React from 'react';
import ReactDOM from 'react-dom';

// 繼承React.Component表示React16是一個組件
class React16 extends React.Component {
    // 構造器函數
    constructor(props){
      super(props)
      this.state={hasError:false}
    }
    // 生命週期函數
    componentDidCatch(error, info) {
      // 設置內部狀態 hasError爲true
      this.setState({ hasError: true })
    }
  
    render() {
      return (
        <div>
          {/*? : 是三目運算符*/}
          {this.state.hasError ? <div>出錯了</div>:null}
          {/*使用組件ClickWithError和FeatureReturnFragments*/}
          <ClickWithError />
          <FeatureReturnFragments />
        </div>
      )
    }
  }
  // 繼承React.Component表示ClickWithError是一個組件
  class ClickWithError extends React.Component{
     constructor(props){
      super(props)
      this.state = {error:false}
      // 綁定handleClick()方法到當前對象上
      this.handleClick = this.handleClick.bind(this)
    }
    handleClick(){
      // 觸發調用時設置state.error值爲true
      this.setState({
        error:true
      })
    }
    render() {
      if (this.state.error) {
        throw new Error('出錯了!')
      }
      return <button onClick={this.handleClick}>拋出錯誤</button>
    }
  }
  // 繼承React.Component表示FeatureReturnFragments是一個組件
  class FeatureReturnFragments extends React.Component{
    render(){
      return [
        <p key="key1">React很不錯</p>,
        "文本1",
        <p key="key2">Antd-desing也很贊</p>,
        "文本2"
     ]
    }
  }
  
ReactDOM.render(<React16/>, document.querySelector('#root'))
複製代碼

虛擬DOM

什麼是DOM?—HTML DOM 教程

DOM操做成本實在是過高,因此有了在js裏模擬和對比文檔對象模型的方案,JSX裏使用 reactcreateElement構建虛擬DOM,每次只要有修改,先對比js裏面的虛擬dom樹裏的內容。 傳統瀏覽器渲染流程圖

虛擬DOM樹結構圖
參考:
react 中文官網
在線學習體驗 react api

實戰來總結

學完了api 的使用,是時候拿起武器開始真刀真槍的開幹了,如圖是實戰的效果演示,具體的代碼分析講解能夠直接在個人github上看到,就不在本文贅述了,我要傳送代碼倉庫===> 項目代碼地址

深刻理解生命週期

React v16.0版本以前

組件初始化階段(initialization)

以下代碼中類的構造方法constructor(),Test類繼承了react Component這個基類,也就繼承這個react的 基類,纔能有render(),生命週期等方法可使用,這也說明爲何函數組件不能使用這些方法的緣由。

super(props) 用來調用基類的構造方法constructor(), 也將父組件的props注入給子組件,供子組件讀取(組件 中props屬性只讀不可寫,state可寫)。 而 constructor() 用來作一些組件的初始化工做,好比定義this.state的初始內 容。

import React, { Component } from 'react';
    class Test extends Component {
        constructor(props) {
        super(props);
    }
}
複製代碼

組件的掛載階段(Mounting)

此階段分爲componentWillMountrendercomponentDidMount三個時期。

  • componentWillMount:在組件掛載到DOM前調用,且只會被調用一次,在這裏面調用this.setState不會引發組件的從新渲染,也能夠把寫在這裏面的內容改寫到constructor()中,因此在項目中不多這麼使用。
  • render:根據組件的propsstate(不管二者是重傳遞或重賦值,不管值是否有變化,均可以引發組件從新render) ,內部return 一個React元素(描述組件,即UI),該元素不負責組件的實際渲染工做,以後由React自身根據此元素去渲染出頁面DOM。render是純函數 (Pure function:函數的返回結果只依賴於它的參數;函數執行過程裏面沒有反作用),不能在裏面執行this.setState等操做,會有改變組件狀態的反作用。
  • componentDidMount:組件掛載到DOM後調用,且只會被調用一次

組件的更新階段(update)

在組件的更新階段中,存在不少生命方法,從上圖能夠很直觀的看到,有 componentWillReceivePropsshouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate

  • componentWillReceiveProps(nextProps):此方法只調用於props引發的組件更新過程當中,參數nextProps是父組件傳給當前組件的新props。但父組件render 方法的調用不能保證重傳給當前組件的props是有變化的,因此在此方法中根據nextPropsthis.props來查明重傳 的props是否改變,以及若是改變了要執行啥,好比根據新的props調用this.setState出發當前組件的從新render
  • shouldComponentUpdate(nextProps,nextState):此方法經過比較nextPropsnextState及當前組件的this.propsthis.state,返回true時當前組件將繼續執行更新過程,返回false則當前組件更新中止,以此可用來減小組件的沒必要要渲染,優化組件性能。 這邊也能夠看出,就算componentWillReceiveProps()中執行了this.setState,更新了state,但在render前 (如shouldComponentUpdatecomponentWillUpdate),this.state依然指向更新前的state,否則nextState 及當前組件的this.state的對比就一直是true了。
  • componentWillUpdate(nextProps, nextState):此方法在調用render方法前執行,在這邊可執行一些組件更新發生前的工做,通常較少用。
  • renderrender方法在上文講過,這邊只是從新調用。
  • componentDidUpdate(prevProps, prevState):此方法在組件更新後被調用,能夠操做組件更新的DOMprevPropsprevState這兩個參數指的是組件更新前的propsstate

在此階段須要先明確下react組件更新機制。setState引發的state更新,或父組件從新render引發的props更新,更新後的stateprops相比較以前的結果,不管是否有變化,都將引發子組件的從新render。詳細瞭解可看=>這篇文章 形成組件更新有兩類(三種)狀況:

  1. 父組件從新render 父組件從新render引發子組件從新render的狀況有兩種

    直接使用,每當父組件從新render致使的重傳props,子組件都將直接跟着從新渲染,不管props是否有變化。可通 過shouldComponentUpdate方法控制優化。

    class Child extends Component {
       // 應該使用這個方法,不然不管props是否有變化都將會致使組件跟着從新渲染
       shouldComponentUpdate(nextProps){ 
           if(nextProps.someThings === this.props.someThings){
               return false
           }
       }
       render() {
           return <div>{this.props.someThings}</div>
       }
    }
    複製代碼
  2. componentWillReceiveProps方法中,將props轉換成本身的state
class Child extends Component {
   constructor(props) {
       super(props);
       this.state = {
           someThings: props.someThings
       };
   }
   componentWillReceiveProps(nextProps) { // 父組件重傳props時就會調用這個方法
       this.setState({someThings: nextProps.someThings});
   }
   render() {
   return <div>{this.state.someThings}</div>
   }
}
複製代碼

根據官網的描述: 在componentWillReceiveProps方法中,將props轉換成本身的state 是由於componentWillReceiveProps中判斷props是否變化了,若變化了,this.setState將引發state變化,從而引 起render,此時就不必再作第二次因重傳props來引發render了,否則就重複作同樣的渲染了。

  1. 組件自己調用setState,不管state有沒有變化。能夠經過shouldComponentUpdate方法控制優化。
shouldComponentUpdate() {
    // 組件是否須要更新,返回布爾值,優化點
    console.log("5.組件是否應該更新?");
    return true;
  }
複製代碼

卸載階段

此階段只有一個生命週期方法:componentWillUnmount此方法在組件被卸載前調用,能夠在這裏執行一些清理工做,好比清楚組件中使用的定時器,清除componentDidMount中手動建立的DOM元素等,以免引發內存泄漏。

React v16.0版本以後(2019.11.20)

原來(React v16.0前)的生命週期在React v16推出的 Fiber以後就不合適了,由於若是要開啓 async rendering, 在render函數以前的全部函數,都有可能被執行屢次。

原來(React v16.0前)的生命週期有哪些是在render前執行的呢?

  • componentWillMount
  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate

若是開發者開了async rendering,並且又在以上這些render前執行的生命週期方法作AJAX請求的話,那AJAX將被 無謂地屢次調用。。。明顯不是咱們指望的結果。並且在componentWillMount裏發起AJAX,無論多快獲得結果 也趕不上首次render,並且componentWillMount在服務器端渲染也會被調用到(固然,也許這是預期的結 果),這樣的IO操做放在componentDidMount裏更合適。

禁止不能用比勸導開發者不要這樣用的效果更好,因此除了shouldComponentUpdate,其餘在render函數以前的 全部函數(componentWillMountcomponentWillReceivePropscomponentWillUpdate)都被 getDerivedStateFromProps替代。

也就是用一個靜態函數getDerivedStateFromProps來取代被deprecate的幾個生命週期函數,就是強制開發者在 render以前只作無反作用的操做,並且能作的操做侷限在根據propsstate決定新的state

React v16.0剛推出的時候,是增長了一個componentDidCatch生命週期函數,這只是一個增量式修改,徹底不影 響原有生命週期函數;可是,到了React v16.3,大改動來了,引入了兩個新的生命週期函數。

新的生命週期函數getDerivedStateFromProps和getSnapshotBeforeUpdate

  • getDerivedStateFromPropsgetDerivedStateFromProps 原本(React v16.3中)是隻在建立和更新(由父組件引起部分),也就是否是不禁 父組件引起,那麼getDerivedStateFromProps也不會被調用,如自身setState引起或者forceUpdate引起。這樣的話理解起來有點亂,在React v16.4中改正了這一點,讓getDerivedStateFromProps不管是Mounting仍是Updating,也不管是由於什麼引發的Updating,所有都會被調用,具體可看React v16.4 的生命週期圖。

static getDerivedStateFromProps(props, state) 在組件建立時和更新時的render方法以前調用,它應該返回 一個對象來更新狀態,或者返回null來不更新任何內容。

  • getSnapshotBeforeUpdategetSnapshotBeforeUpdate() 被調用於render以後,能夠讀取但沒法使用DOM的時候。它使您的組件能夠在可 能更改以前從DOM捕獲一些信息(例如滾動位置)。今生命週期返回的任何值都將做爲參數傳遞給 componentDidUpdate()。
class ScrollingList extends React.Component {
   constructor(props) {
       super(props);
       this.listRef = React.createRef();
   }
   getSnapshotBeforeUpdate(prevProps, prevState) {
       //咱們是否要添加新的 items 到列表?
       // 捕捉滾動位置,以便咱們能夠稍後調整滾動.
       if (prevProps.list.length < this.props.list.length) {
           const list = this.listRef.current;
           return list.scrollHeight - list.scrollTop;
       }
       return null;
   }
   componentDidUpdate(prevProps, prevState, snapshot) {
       //若是咱們有snapshot值, 咱們已經添加了 新的items.
       // 調整滾動以致於這些新的items 不會將舊items推出視圖。
       // (這邊的snapshot是 getSnapshotBeforeUpdate方法的返回值)
       if (snapshot !== null) {
           const list = this.listRef.current;
           list.scrollTop = list.scrollHeight - snapshot;
        }
   }
   render() {
       return (
           <div ref={this.listRef}>{/* ...contents... */}</div>
       );
   }
}
複製代碼

掃碼關注公衆號,回覆20191120獲取本文全部源碼

☞☞點擊這裏購買雲服務器☜體驗代碼效果☜

相關文章
相關標籤/搜索