React 基礎內容

一. 使用React編寫todolist

1. 佔位符Fragment;

先從下面圖片開始瞭解本節的目錄結構、代碼、效果:

目錄結構:

代碼:

index.js

TodoList.js

效果:

上圖中紅色方框中的dom結構是用一個div包裹的,但是有時候我們在實現flex佈局的時候該div包裹會影響我們的樣式。React提供了一個佔位符Fragment。我們引入該佔位符,同時將最外層的div包裹替換成Fragment即可。

 

修改後的代碼:

TodoList.js

 

效果:

 


二. React中的響應式設計思想和事件綁定

        如果用原生js或jquery來做todolist的話,我們一般是獲取輸入框的value值,然後通過document.getElementById獲取列表區域,然後將value值掛載到列表區域。

        React與上面直接操作dom的形式不同。它是一個響應式的框架,通過操作數據,React會感知到數據的變化,自動地幫你去生成dom。所以寫React項目的時候,我們只需要關注數據層的變化。

 

1. Constructor

        在js中,一個對象或者類就有一個constructor構造函數。在React中,當我們去創建或者使用TodoList這個組件的時候,constructor這個構造函數時優於別的任何函數的,自動的最先被執行的一個函數。所以我們可以在該構造函數裏定義數據。

  

 

2. 如何雙向綁定

以todolist的input框的value值爲例,我們將它和todolist狀態中的inputValue進行綁定。

<input value={this.state.inputValue}/>

想在JSX語法中使用js的變量(如this.state.inputValue),我們需要用大括號將其括起來。

 

3. React事件綁定

以監聽input框的change事件爲例,原生js是onchange, 在React中應該寫成onChange。

TodoList.js:

import React, { Component, Fragment } from 'react';

class TodoList extends Component {
  constructor(props) {
    //固定寫法,意思是TodoList是繼承自React的component這個組件,我們這要調用一次父類的構造函數
    super(props);
    //React的數據需要定義在狀態裏面,下面的state就是表示組件的狀態
    this.state = {
      inputValue: 'hahah',  //input框的value值
      list: []        //數組中的每一項
    }
  }

  render() {
    return (   //jsx語法要求render函數返回的內容外層必須用一個標籤包裹
      <Fragment>
        <div>
          <input 
            value={this.state.inputValue}
            onChange={this.handleInputChange}
          />
          <button>提交</button>
        </div>
        <ul>
          <li>學英語</li>
          <li>學React</li>
        </ul>
      </Fragment>
    )
  }

  handleInputChange(e) {
    console.log(e.target.value);   //控制檯會打印出第一次修改後的value值,但是input框內容未改變
  }
}

export default TodoList;   //導出TodoList組件

按照常理,我們想input框的value跟着改變的話,加入如下代碼即可:

handleInputChange(e) {
    this.state.inputValue = e.target.value;   //Cannot read property 'state' of undefined
}

實際上,上面的this並不是指向TodoList這個組件,打印this結果是undefined。我們可以通過bind來更改this的指向。

<input 
            value={this.state.inputValue}
            onChange={this.handleInputChange.bind(this)}
/>

handleInputChange(e) {
  this.state.inputValue = e.target.value;  //此時this指向的是該組件,但是還是不能改變輸入框的值
}

//原因是React不能直接通過this.state來改變state裏面的值,它提供了一個方法:this.setState({})

最終解決辦法:

handleInputChange(e) {
    this.setState({
      inputValue: e.target.value
    })
}

 


三. 實現TodoList新增刪除功能

import React, { Component, Fragment } from 'react';

class TodoList extends Component {
  constructor(props) {
    //固定寫法,意思是TodoList是繼承自React的component這個組件,我們這要調用一次父類的構造函數
    super(props);
    //React的數據需要定義在狀態裏面,下面的state就是表示組件的狀態
    this.state = {
      inputValue: '',  //input框的value值
      list: []        //數組中的每一項
    }
  }

  render() {
    return (   //jsx語法要求render函數返回的內容外層必須用一個標籤包裹
      <Fragment>
        <div>
          <input 
            value={this.state.inputValue}
            onChange={this.handleInputChange.bind(this)}
          />
          <button onClick={this.handleBtnClick.bind(this)}>提交</button>
        </div>
        <ul>
          {
            this.state.list.map((item, index) => {
              return (
                <li 
                  key={index}
                  onClick={this.handleItemDelete.bind(this, index)}
                >{item}</li>
              )
            })
          }
        </ul>
      </Fragment>
    )
  }

  //輸入框value發生改變
  handleInputChange(e) {
    this.setState({
      inputValue: e.target.value
    })
  }

  //點擊提交將value加入列表項
  handleBtnClick() {
    this.setState({
      //es6的展開運算符,將this.state.list該數組展開成參數列表的形式(如:[...[1, 2]] => [1, 2])
      list: [...this.state.list, this.state.inputValue],
      inputValue: ''
    })
  }

  //點擊列表項刪除
  handleItemDelete(index) {
    //immutable, React不允許我們直接對state做任何修改
    const list = this.state.list;
    list.splice(index, 1);
    this.setState({
      list: list
    })
  }
}

export default TodoList;   //導出TodoList組件

 

四、JSX語法細節補充

 

1、如何在JSX語法中編輯註釋。

我們只需要將註釋用花括號包裹即可。注意:單行註釋應該重起一行。

 

2、在React中引入外部樣式。

新建樣式文件style.css

在TodoList.js中引入

給input框添加類名

結果顯示樣式發生了改變,但是控制檯報錯:

這是因爲React爲了避免將input中的class與類相混淆,不推薦我們使用class來表示元素的類名,推薦我們使用className來代替。

將上面的class修改成className,樣式發生了改變且控制檯不報錯。

 

3、對輸入框輸出內容進行轉義。

對於上圖的結果,我們希望輸出結果中的h1被解析轉義出來。代碼需進行如下修改:

最終結果如下所示:

 

4、點擊標籤左邊文字使光標聚焦在輸入框。

在HTML中label標籤的作用是擴大輸入框的點擊區域。我們需要實現一個功能:點擊輸入內容,讓光標聚焦在輸入框。

添加如下代碼實現:

效果如下:

但是控制檯報錯:

原因和上面一樣,也是React爲了避免label標籤中的for屬性和js的for語句混淆,不推薦使用for來關聯input的id。使用htmlFor來替換for。

效果一樣,同時控制檯也不報錯。

 


五. 拆分組件與組件之間的傳值

1. 將TodoList這個組件拆分成兩個組件

首先在src目錄下創建一個TodoItem.js組件

輸入內容如下:

在TodoList中引入該組件:

修改TodoList中的JSX:

注意:應該用一個元素包裹TodoItem與註釋,否則會報錯。

結果界面:

顯然這不是我們想要的結果,這是因爲我們將TodoItem裏面內容寫死了。要實現我們的功能,我們需要父組件向子組件傳值

 

2. 父組件向子組件傳值

通過屬性的方式進行傳值,子組件通過this.props接收。具體例子如下:

通過屬性傳值:

通過this.props接收:

結果界面:

 

3. 子組件操作父組件的數據

接下來實現點擊每個列表項自動刪除的功能:

首先子組件需要獲取被點擊那一項的index值:

接下來我們需要將該值傳遞給父組件的handleItemDelete方法,刪除數組中對應index的那一項。

父組件:

子組件:

此時,當我們點擊列表項的時候報錯

原因是因爲我們子組件中調用this.props.deleteItem方法實際上就是調用父組件的this.handleItemDelete方法,但是這裏的this指向是子組件,我們需要強制改變this的指向,使它指向父組件。

最後功能成功實現了,也沒有報錯。

 


六. TodoList代碼優化

1. TodoItem.js代碼優化

利用ES6的解構賦值對代碼優化:

優化前:

優化後:

 

2. TodoList.js代碼優化

主要優化部分:

      1. 在constructor部分改變this的指向

      2.將JSX語法中的邏輯部分用一個方法來表示

      3.使用新版React推薦的setState語法(不直接返回一個對象,而是返回一個函數,函數內再return一個對象)

      4.解決使用循環帶來的key值問題

優化前:

import React, { Component, Fragment } from 'react';
import './style.css';
import TodoItem from './TodoItem';

class TodoList extends Component {
  constructor(props) {
    //固定寫法,意思是TodoList是繼承自React的component這個組件,我們這要調用一次父類的構造函數
    super(props);
    //React的數據需要定義在狀態裏面,下面的state就是表示組件的狀態
    this.state = {
      inputValue: '',  //input框的value值
      list: []        //數組中的每一項
    }
  }

  render() {
    return (
      <Fragment>
        <div>
          <label htmlFor="insert">輸入內容</label>
          <input
            id="insert" 
            value={this.state.inputValue}
            onChange={this.handleInputChange.bind(this)}
            className='input'
          />
          <button onClick={this.handleBtnClick.bind(this)}>提交</button>
        </div>
        <ul>
          {
            this.state.list.map((item, index) => {
              return (
                <div>
                  <TodoItem 
                    content={item}
                    index={index}
                    deleteItem={this.handleItemDelete.bind(this)}
                  />
                </div>
              )
            })
          }
        </ul>
      </Fragment>
    )
  }

  //輸入框value發生改變
  handleInputChange(e) {
    this.setState({
      inputValue: e.target.value
    })
  }

  //點擊提交將value加入列表項
  handleBtnClick() {
    this.setState({
      //es6的展開運算符,將this.state.list該數組展開成參數列表的形式(如:[...[1, 2]] => [1, 2])
      list: [...this.state.list, this.state.inputValue],
      inputValue: ''
    })
  }

  //點擊列表項刪除
  handleItemDelete(index) {
    //immutable, React不允許我們直接對state做任何修改
    const list = this.state.list;
    list.splice(index, 1);
    this.setState({
      list: list
    })
  }
}

export default TodoList;   //導出TodoList組件

優化後:

import React, { Component, Fragment } from 'react';
import './style.css';
import TodoItem from './TodoItem';

class TodoList extends Component {
  constructor(props) {
    //固定寫法,意思是TodoList是繼承自React的component這個組件,我們這要調用一次父類的構造函數
    super(props);
    //React的數據需要定義在狀態裏面,下面的state就是表示組件的狀態
    this.state = {
      inputValue: '',  //input框的value值
      list: []        //數組中的每一項
    };
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleBtnClick = this.handleBtnClick.bind(this);
    this.handleItemDelete = this.handleItemDelete.bind(this);
  }

  render() {
    return (
      <Fragment>
        <div>
          <label htmlFor="insert">輸入內容</label>
          <input
            id="insert" 
            value={this.state.inputValue}
            onChange={this.handleInputChange}
            className='input'
          />
          <button onClick={this.handleBtnClick}>提交</button>
        </div>
        <ul>
          {this.getTodoItem()}
        </ul>
      </Fragment>
    )
  }

  //輸入框value發生改變
  handleInputChange(e) {
    const value = e.target.value;
    this.setState(() => ({
      inputValue: value
    }));
  }

  //點擊提交將value加入列表項
  handleBtnClick() {
    this.setState((prevState) => ({
      //prevState指的是修改數據之前那次數據,該寫法可以避免我們有時候不小心改變了state的狀態
      list: [...prevState.list, prevState.inputValue],
      inputValue: ''
    }));
  }

  //遍歷list,顯示每一個item
  getTodoItem() {
    return this.state.list.map((item, index) => {
      return (
        <TodoItem 
          content={item}
          index={index}
          key={index}
          deleteItem={this.handleItemDelete}
        />
      )
    })
  }

  //點擊列表項刪除
  handleItemDelete(index) {
    this.setState((prevState) => {
      const list = prevState.list;
      list.splice(index, 1);
      return {list};
    })
  }
}

export default TodoList;   //導出TodoList組件

 


七. 圍繞React衍生出的一些思考

1. 命令式開發、聲明式開發

命令式開發: 我們之前使用的jquery開發都是直接操作dom,我們把這種方式稱爲命令式開發。就是我們開發的時候,需要告訴頁面,你要如何去獲取,接着又如何去掛載,這種命令式的方式。

聲明式開發:React就是聲明式的開發,比如我們要開發一個網頁,之前命令式的開發需要一步一步地去指導如何去開發。而React不是這樣的,它是面向數據來編程的,我們只需要把數據構建出就可以,React會自動根據數據去構建這個網頁。數據就類似一個圖紙,React根據圖紙會自動地幫你去構建這棟房子。

 

2.可以與其它框架並存

在React項目的入口文件,我們將TodoList這個組件掛載到id等於root的元素下面,我們開發過程中,第三方框架在id等於root的元素之外使用是不會影響到React的,同樣React也不會影響其他的框架。

 

3. 組件化

4. 單向數據流

父組件可以傳數據給子組件,但是不允許子組件直接改變傳遞過來的數據。這是因爲當該數據被多個組件使用的時候,我們子組件改變了該數據,別的組件使用該數據時也被改變了,而且在出現bug時不容易定位。

 

5.視圖層的框架

因爲React在開發大型項目的時候,組件樹比較複雜,單靠React父子組件傳值很麻煩,我們需要引用別的像Redux這樣的數據層的框架來輔助我們開發。React只負責解決頁面與數據渲染上的一些問題,至於組件之間如何傳值,它並不負責。對於Todolist這種小型的項目,藉助React內部的傳值機制就可以,但是大中型項目,這時遠遠不夠的。

 

6. 函數式編程

我們在React文件中可以看到,React開發都是一個函數一個函數地去編寫。這帶來了幾個好處:

    1. 維護起來容易。函數比較大地時候可以進行拆分,每個函數各司其職。

    2. 方便自動化測試。我們只需要給函數一個輸入值,看輸出值是否正確,給前端自動化測試帶來了很大地便捷性。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章