react 知識梳理(一)

易名發表於2018-03-08

react 知識梳理(一)

這幾天讀了鬍子大哈老師《React.js 小書》,感覺獲益良多,真心推薦大家可以看一看!


示例程式碼請點這裡


JSX 原理

jsx 的本質是 React.createElement 的語法糖,在 React 文件中,有這麼一段話:

JSX 對使用React 不是必須的。當你不想在你的構建環境中設定編譯器,那麼不使用 JSX 的 React 是非常方便的。

每一個 JSX 元素都是呼叫 React.createElement(component, props, ...children) 的語法糖,因此,任何你使用 JSX 來做的事都可以通過純 JavaScript 實現。

<div class="root">
  <div class="child">hello man</div>
</div>
複製程式碼

觀察以上程式碼,我們可以知道,每個 DOM 其實都只包含了三個資訊:標籤名稱、屬性、子元素。因此,每個 DOM 元素,我們都可以用一個 js 物件來標示。上面這段程式碼我們可以這樣表示:

{
  tag: 'div',
  attr: {
    class: 'root'
  },
  child: {
    tag: 'div',
    attr: {
      class: 'child'
    },
    child: 'hello man'
  }
}
複製程式碼

在 React 中,我們使用 React.createElement 來將上面的這種 js 物件來轉化成為真正的 DOM 元素,至於 React.createElement 的內部實現,這裡暫不做深究。

如果我們直接在程式碼中呼叫 React.createElement ,通過這種 js 物件的方式來生成 DOM 的話,程式碼看起來會有些冗長繁瑣,不太符合我們追求簡單的想法,於是,React 發明了 JSX 語法,通過 JSX ,我們可以直接在 js 程式碼中書寫 html 結構,最後交給 babel 來編譯為上面這種 js 描述 DOM 的物件,最後通過 React.createElement 來構建真正的 DOM。

容器元件

在一些業務場景下,我們或許需要處理大量的具有相同整體佈局可是包含子節點的佈局內容卻又完全不同的 DOM,例如下圖所示:

react 知識梳理(一)

我們可以看到,雖然左右兩個元素中的內部元素完全不同,可是卻又有著相同的整體佈局!這個時候,雖然我們可以用相同的命名,引入相同的 css 來減少我們的工作量,可是,當這些元素分別位於不同的元件時,我們就要為每個元件都引入同一份 css ,還要給他們同樣的類名,顯然,這樣有些繁瑣。如果這時用一個可以公用的容器元件,我們只需要引入這個容器元件,然後往裡邊填充相應的內容即可,顯然可以讓程式碼顯得更加整潔,使用起來也更加方便。

我們用 create-react-app 新建一個 React 專案(示例程式碼請點這裡),清除掉一些不相干的內容,讓我們的專案看起來更加清晰,然後新建兩個元件:

react 知識梳理(一)

Container.js

import React from 'react';

export default class Container extends React.Component {
  constructor(){
    super()
  }
  render(){
    return (
      <div>
        Container
      </div>
    )
  }
}
複製程式碼

head.js

import React from 'react';

export default class Head extends React.Component {
  constructor() {
    super()
  }
  render() {
    return (
      <div>
        head
      </div>
    )
  }
}
複製程式碼

我們在 index.js 中引入這兩個元件,將 Head 元件插入 Container 元件中:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Container from './container';
import Head from './head';


ReactDOM.render(
  <Container>
    <div>hello man</div>
    <Head></Head>
  </Container>,
  document.getElementById('root')
);

複製程式碼

開啟瀏覽器,我們看到,在頁面上,並沒有出現 head ,也沒有出現 hello man,而是顯示出了 container :

react 知識梳理(一)

我們在控制檯將 container 列印出來:

react 知識梳理(一)

可以看到在 container 的 props.children 屬性中,儲存著我們插入進 Container 元件的內容,我們只需要將這些資訊渲染出來就可以了,我們改寫一下 Container 元件的程式碼:

import React from 'react';

export default class Container extends React.Component {
  constructor(){
    super()
  }
  render(){
    return (
      <div className="container">
        <div className="head">{this.props.children[0]}</div>
        <div className="body">{this.props.children[1]}</div>
      </div>
    )
  }
}
複製程式碼

開啟瀏覽器,我們看到,之前填充在 Container 元件中的內容已經正常顯示,並且整體佈局結構也和我們預期一樣。我們可以根據具體的業務,來填充具體的內部元素。

這裡有個問題需要注意,當我們註釋掉插入 Container 元件的 Head 元件時,我們發現,插入的 hello man 也不能正常顯示:

ReactDOM.render(
  <Container>
    <h1>hello man</h1>
    {/* <Head></Head> */}
  </Container>,
  document.getElementById('root')
);
複製程式碼

react 知識梳理(一)
我們在控制檯重新列印出 Container 元件的資訊:
react 知識梳理(一)
我們看到,此時的 props.children 已經不再是一個陣列,而是一個物件,所以我們在 Container 元件內部通過下標來取值的做法,顯然是有問題的。我們需要來對props.children進行判斷,來確定取值方式,具體實現並不難,這裡就不再寫示例程式碼了。

高階元件(文件位置)

首先,我們來看一下這些定義:

高階元件是一個函式,能夠接受一個元件並返回一個新的元件。
高階元件是一個純函式,不會改變原來的元件,沒有副作用。

簡單來說,滿足下面條件的函式可以稱之為純函式:

返回結果完全依賴傳入的引數。
執行過稱中沒有副作用。
同樣的輸入得到同樣的輸出。const gen = Math.random() 就不是純函式。(感謝FateRiddle指正)

我們來看下面這兩個函式,瞭解下什麼是返回結果完全依賴傳入的引數:

let d = 1;
function add(a,b){
  return a + b
}

function all(c){
  return c + d
}
複製程式碼

當我們執行 addall 時,add 的返回結果完全依賴於傳入的引數 ab 的數值,add(3,6) 一定返回 9,可是當 all 執行時, all(3) 的返回之就不一定是 4,當 d 的值變為 2 的時候, all(3) 的返回值就變成了 5,所以,在這裡 all 就不能稱之為是一個純函式,而 add 則是一個純函式。

可是當我們重新宣告一個變數,改寫下add

let obj = {
  x: 2
}
function add(obj,b){
  obj.x = 1;
  return obj.x + b
}
複製程式碼

我們再次呼叫 add(obj,6),這個時候,雖然 add 的返回結果依舊完全依賴與傳入的引數,但是,傳入的 obj 物件的 x 屬性的值卻發生了變化,這就是產生的副作用,所以,add 就不是一個純函式。

高階函式是一個純函式,不會改變傳入的元件。

說了這麼多,可是高階元件有哪些用處呢?

我們來看下面這兩段程式碼:

元件一:

import React from 'react';
export default class One extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      higher: 0
    }
  }
  componentWillMount(){
    let higher = this.props.higher * 2
    this.setState({
      higher: higher
    })
  }
  render() {
    return (
      <div>{this.state.higher}</div>
    )
  }
}

元件二:

import React from 'react';
export default class Two extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      higher: 0
    }
  }
  componentWillMount(){
    let higher = this.props.higher * 2
    this.setState({
      higher: higher
    })
  }
  render() {
    return (
      <h1>{this.state.higher}</h1>
    )
  }
}
複製程式碼

我們看到,除了元件返回的標籤元素外,其他程式碼完全相同。兩個元件都在 componentWillMount 時對傳入的資料做了邏輯處理,這時,我們就可以利用高階元件,將公共的邏輯程式碼抽離出來:

const higher = function(Component,data){
  class Higher extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        higher: 0
      }
    }
    componentWillMount() {
      let higher = data * 2
      this.setState({
        higher: higher
      })
    }
    render() {
      return (
        <Component higher={this.state.higher}></Component>
      )
    }
  }

  return Higher
}
複製程式碼

我們將原始元件和高階元件插入頁面:

let CopyOne = higher(One,30);
let CopyTwo = higher(Two, 40);

ReactDOM.render(
  <div>
    <CopyOne></CopyOne>
    <CopyTwo></CopyTwo>
    <One higher={10}></One>
    <Two higher={20}></Two>
    {/* <Container>
      <h1>hello man</h1>
      <Head></Head>
    </Container> */}
  </div>,
  document.getElementById('root')
);
複製程式碼

開啟瀏覽器我們看到:

react 知識梳理(一)
通過高階元件函式生成的元件和原始元件都被渲染出來,可以看到,高階元件函式根據傳入的原始元件,生成了不同的元件,並且沒有改變原始元件。
高階元件的使用,極大的提高了我們程式碼的複用性,在高階元件函式內部,我們可以根據具體業務,十分靈活的對元件進行改造,生成符合我們業務需求的新元件。

最後的話:

這篇筆記簡要的梳理了一下自己對 React 的理解,若有描述不對之處,還望大家批評指正!

相關文章