React 基礎入門 (一)

前面的話

這是一篇算React入門級的文章,初次接觸React,有問題請指出~~~。

安裝方式

這裏介紹搭建一個 react 項目的兩種方式: CDN鏈接 和 官方推薦的 create-react-app 腳手架

CDN 鏈接

創建一個 index.html 文件,將 React、React DOM、Babel 這三個 CDN 鏈接引入到 head 標籤中。並創建一個 div ,id爲 root,作爲根元素。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello React</title>
    <!-- React --- React頂級API -->
    <script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
    <!-- React DOM --- 添加特定與DOM的方法 -->
    <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
    <!-- Babel ---js編譯器,可使用es6+ -->
    <script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
    <div id="root"></div>
    <!--  text/babel 腳本類型是babel所必須的 -->
    <script type="text/babel">
        // react code
    </script>
</body>
</html>

引入這三個 CDN 鏈接的目的:

  • React – 頂級API
  • React DOM —添加特定DOM的方法
  • Babel — 種JavaScript編譯器, 可使用ES6+

如果需要安裝指定的版本,將@後面的版本號改爲指定版本。

⚠️注意:script 腳本的類型 text/babel是必須的

我們都知道,不管是 Vue 還是 React 都是組件化,在 React 中有兩種創建組件的方法,Class 組件和 Function 組件, 這裏我們創建一個 Class 組件。

創建一個 Class 組件很簡單,只要創建一個 Class 類,並讓他繼承 React.Component, 在render() 方法中 return 一個 JSX 模版,用於呈現 DOM節點。

⚠️注意:render() 方法是類組件唯一需要的方法。

創建組件App :

// 創建組件App
        class App extends React.Component {
            // render方法必須,用於呈現DOM節點
            render() {
                return (
                    // jsx 
                    <h1>hello word</h1>
                )
            }
        }
    

return 中, 可以看到返回很像 HTML 的內容,但其不是 HTML ,而是 JSX ,這裏先不過多介紹。

最後,我們使用 ReactDOM.render() 方法將 App組件渲染到 root根元素 div 中:

    ReactDOM.render(<App/>, document.getElementById('root'));

index.html 的完整代碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello React</title>
    <!-- React --- React頂級API -->
    <script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
    <!-- React DOM --- 添加特定與DOM的方法 -->
    <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
    <!-- Babel ---js編譯器,可使用es6+ -->
    <script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
    <div id="root"></div>
    <!--  text/babel 腳本類型是babel所必須的 -->
    <script type="text/babel">
        // 創建組件App
        class App extends React.Component {
            // render方法必須,用於呈現DOM節點
            render() {
                return (
                    // jsx 
                    <h1>hello word</h1>
                )
            }
        }
        ReactDOM.render(<App/>, document.getElementById('root'));
    </script>
</body>
</html>

在瀏覽器中打開 index.html 文件:
在這裏插入圖片描述

這樣一個簡單的 React 應用就搭建好了。

create-react-app

第一種搭建 react 項目的方法只適合書寫簡單的react 代碼,使用 create-react-app腳手架可以幫我們初始化一個項目 demo。

npx create-react-app  react-start

項目結構:public/index.html 是頁面模板,src/index.js 則是入口JS文件

在這裏插入圖片描述
安裝完成後,進入項目文件並啓動:

cd react-start

npm start 

運行後,會彈出新窗口:
在這裏插入圖片描述

下面修改一下代碼,修改App.js 文件爲上面創建 App 組件的代碼:

import React, { Component } from 'react';
// import './App.css';

class App extends Component {
  render() {
    return <h1>Hello, React</h1>;
  }
}
export default App;

在這裏插入圖片描述

什麼是JSX?

上面的代碼中已經接觸了 JSX,看起來很像 HTML ,但是並不是。它是JSX,代表 JavaScript XML 。

接着上面的代碼,修改App.js :

const name = 'React';
const element = (
  <div tabIndex="0" className="box">
    <h1>Hello, {name}</h1>
    <div className="name">
      <span>xiaoqi</span>
    </div>
  </div>
);
class App extends Component {
  render() {
    return element;
  }
}

element 就是一個 JSX,可以看到它的特點:

  • 可以使用 { } 內置 js 表達式
  • JSX 標籤可以包含很多子標籤
  • JSX 中可以指定特定的屬性(採用大駝峯),比如上面的 tabIndexclassName

看一下渲染之後的效果:

在這裏插入圖片描述

元素渲染

元素是構成 React 應用的最小磚塊。

創建一個元素 element :

const element = (
  <div className="box">
    <h1>React 入門</h1>
    <span> 元素是構成 React 應用的最小磚塊。</span>
  </div>
);

將這個元素渲染到 DOM 根結點中,需要使用 ReactDOM.render() 方法:

ReactDOM.render(element, document.getElementById('root'));

因 index.js 文件是項目的 js 入口文件,所以我們修改 index.js 文件爲上面的代碼:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// import App from './App';
// import * as serviceWorker from './serviceWorker';

const element = (
  <div className="box">
    <h1>React 入門</h1>
    <span> 元素是構成 React 應用的最小磚塊。</span>
  </div>
);
ReactDOM.render(element, document.getElementById('root'));

運行結果:
在這裏插入圖片描述

組件

組件分爲兩種: Class 組件和 Function 組件。

大多數的 React 應用都由很多小組件組成,將所有的小組件都添加到App主組件中,最後將 App主組件渲染到入口 js 文件 index.js 中。

我們將修改 App.jsindex.js 文件:

App.js:

import React, { Component } from 'react';
class App extends Component {
  render() {
    return (
      <div className="app">
        <h1> Hello, React</h1>
      </div>
    );
  }
}
export default App;

index.js:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './index.css'

ReactDOM.render(<App />, document.getElementById('root'))

Class 組件

前面提過,創建一個 Class 組件,只需要創建一個 class 類,並且讓它繼承React.Component, 在 render() 方法中 returnJSX模版。

接下來,我們在 src 文件夾中 ,創建一個 Table.js文件:

// Table組件
import React, { Component } from 'react';

class Table extends Component {
  render() {
    return (
      <table>
          <thead>
          <tr>
            <th>Name</th>
            <th>Job</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Charlie</td>
            <td>Janitor</td>
          </tr>
          <tr>
            <td>Mac</td>
            <td>Bouncer</td>
          </tr>
          <tr>
            <td>Dee</td>
            <td>Aspiring actress</td>
          </tr>
      </table>
    );
  }
}
export default Table;

在 App.js中添加 Table組件:

class App extends Component {
  render() {
    return (
      <div className="app">
        <h1> Hello, React</h1>
        <Table />
      </div>
    );
  }
}

運行結果:

在這裏插入圖片描述

Function 組件

上面的 Table 組件, 我們可以拆分爲兩個子組件:TableBody組件和TableHeader組件, 這兩個組件我們使用 Function 組件來創建:

修改 Table.js : TableHeaderTableBody組件都在Table.js文件中,並被 Table組件使用。

// TableHeader組件
const TableHeader = () => {
  return (
    <thead>
      <tr>
        <th>Name</th>
        <th>Job</th>
      </tr>
    </thead>
  );
};

// TableBody 組件
const TableBody = () => {
  return (
    <tbody>
      <tr>
        <td>Charlie</td>
        <td>Janitor</td>
      </tr>
      <tr>
        <td>Mac</td>
        <td>Bouncer</td>
      </tr>
      <tr>
        <td>Dee</td>
        <td>Aspiring actress</td>
      </tr>
    </tbody>
  );
};

class Table extends Component {
  render() {
    return (
      <table>
        <TableHeader />
        <TableBody />
      </table>
    );
  }
}

小結

比較一下 Function 組件與Class 組件:

Function 組件:

const functionComponent = () => {
	return  <div> Example</div>
}

Class 組件:

class ClassComponent entends React.Component {
	render() {
		return <div> Example</div>
	}
}

組件通信: Props

Vue中的父傳子使用的是 props,React 中的父傳子也是props,下面看一下在React中props是如何使用的。

如果表格的數據很多,那麼上面的TableBody 組件會顯的很臃腫。我們將< tbody>中要輸出的數據提取出來,通過props的方式來進行傳遞。

修改App.js文件:

  • 在App組件中聲明數據 characters, 表示< tbody>中要輸出的數據。

  • 在子組件 Table上添加屬性名稱和數據

    class App extends Component {
      render() {
        // 提取數據
        const characters = [
          {
            name: 'Charlie',
            job: 'Janitor',
          },
          {
            name: 'Mac',
            job: 'Bouncer',
          },
          {
            name: 'Dee',
            job: 'Aspring actress',
          },
        ];
        return (
          <div className="app">
            <h1> Hello, React</h1>
            <Table characterData={characters} />
          </div>
        );
      }
    }
    

修改 Table.js :

  • 子組件中的使用:在 class 組件中通過 this.props.屬性名來獲取父組件中傳遞的數據:

    class Table extends Component {
      render() {
        const { characterData } = this.props;
        return (
          <table>
            <TableHeader />
            <TableBody characterData={characterData} />
          </table>
        );
      }
    }
    

    這裏的 const { characterData } = this.props 相當於 const characterData = this.props.characterData,這裏只是es6的寫法。

  • 繼續將數據傳遞給 Table 組件的子組件 TableBody: TableBody是一個 function組件,其使用 props 的方法直接是:將props作爲參數名傳遞給function組件,使用 props.屬性名即可獲取父組件的數據

    // TableBody 組件
    const TableBody = (props) => {
      const rows = props.characterData.map((item, index) => {
        return (
          <tr key={index}>
            <td>{item.name}</td>
            <td>{item.job}</td>
          </tr>
        );
      });
      return <tbody>{rows}</tbody>;
    };
    

    上面的例子中,給每個錶行都添加了一個 key。React 中創建列表時,使用都要使用key,來識別列表的每一項項,這是必要的。

⚠️注意props 具有隻讀性,無論是函數聲明還是class聲明的組件,都不能修改自身的props

State狀態

props 是單向傳遞,並且是隻讀的。如果想改變數據的狀態,可以使用state,與props 不同的是state可變,但是 state 是class組件私有的對象,修改state 不能直接修改,要使用this.setState() 方法來改變。

現在我們實現一個功能:在表的每行添加一個刪除按鈕,點擊之後,可刪除該行信息。

修改 App.js

  • 創建一個 state 對象,包含存儲的數據 characters
  • 由於要刪除數據,即要改變 state 對象,我們添加一個 removeCharacter() 方法,在該方法內實現 state 數據的變化,通過數組的索引過濾,然後返回新的數組。
  • 最後將該函數傳遞給子組件Table
class App extends Component {
  state = {
    characters: [
      {
        name: 'Charlie',
        job: 'Janitor',
      },
      {
        name: 'Mac',
        job: 'Bouncer',
      },
      {
        name: 'Dee',
        job: 'Aspring actress',
      },
    ],
  };
  // 箭頭函數解決 class中this不綁定的問題
  removeCharacter = (i) => {
    this.setState({
      characters: this.state.characters.filter((item, index) => {
        return i !== index;
      }),
    });
  };

  render() {
    // 提取數據
    return (
      <div className="app">
        <h1> Hello, React</h1>
        <Table
          characterData={this.state.characters}
          removeCharacter={this.removeCharacter}
        />
      </div>
    );
  }
}

將函數removeCharactor傳遞給Table 組件之後,還需要傳遞給TableBody組件,並且還要在 TableBody組件中給每行添加一個按鈕,點擊之後執行此函數。

修改 Table.js:

// Table組件
import React, { Component } from 'react';

// TableHeader組件
const TableHeader = () => {
  return (
    <thead>
      <tr>
        <th>Name</th>
        <th>Job</th>
      </tr>
    </thead>
  );
};
// TableBody 組件
const TableBody = (props) => {
  const rows = props.characterData.map((item, index) => {
    return (
      <tr key={index}>
        <td>{item.name}</td>
        <td>{item.job}</td>
        <td>
          <button onClick={() => props.removeCharacter(index)}>Dalete</button>
        </td>
      </tr>
    );
  });
  return <tbody>{rows}</tbody>;
};

class Table extends Component {
  render() {
    const { characterData, removeCharacter } = this.props;
    return (
      <table>
        <TableHeader />
        <TableBody
          characterData={characterData}
          removeCharacter={removeCharacter}
        />
      </table>
    );
  }
}

該onClick函數必須通過返回該removeCharacter()方法的函數,否則它將嘗試自動運行。

運行結果:
在這裏插入圖片描述

⚠️注意: State 的更新可能是異步的, React 可能會把多個setState() 調用合併成一個調用。

props 與 state的區別
  • state 是管理數據,控制狀態,可變(通過this.setState() )
  • props 是外部傳入的數據參數,不可變
  • 沒有 state 的叫無狀態組件,有 state 的叫有狀態組件
  • 多用 props,少用 state。

事件處理

React 中事件處理和 DOM 元素的很相似,但是在語法上有些不同:

  • React 事件的命名採用小駝峯(camelCase)
  • 使用 JSX 語法時需要傳入一個函數作爲事件處理函數

例子:

<button onClick={activateLasers}>
  Activate Lasers
</button>

前面,我們已經實現了將數據存儲在state狀態中,並且可以從狀態中刪除任何數據。但如果我們想添加新的數據怎麼辦呢?

接下來,通過下面的表單例子,來體驗 React 中的事件處理。我們添加一個 Form.js 組件, 每次更改表單中字段時會更新狀態,並且在我們提交時,所有數據會傳遞給 App組件的 state,進而更新整個 Table。

創建新文件 Form.js :

  • 我們將表單的初始狀態設置爲帶有空屬性的對像,並且將初始狀態賦給 this.state:

    import React, { Component } from 'react'
    
    class Form extends Component {
      initialState = {
        name: '',
        job: '',
      }
    
      state = this.initialState
    }
    
  • 表單內容大致如下:

    render() {
        const { name, job } = this.state; // 初始化時爲空
        return (
          <form>
            <label htmlFor="name">name</label>
            <input
              type="text"
              name="name"
              id="name"
              value={name}
              onChange={this.handleChange}
            />
            <label htmlFor="job">job</label>
            <input
              type="text"
              name="job"
              id="job"
              value={job}
              onChange={this.handleChange}
            />
          </form>
        );
      }
    
  • 每次輸入時都更改 state 裏面的數據,所以在onChange 事件發生時,將 handleChangr() 方法作爲回調傳入

    handleChange = (event) => {
        const { name, value } = event.target;
        this.setState({
          [name]: value,
        });
      };
    

⚠️注意:

在class 組件中,class 的方法默認不會綁定 this,所以要謹慎 JSX 回調函數中的 this 。例如上面的例子中,如果忘記綁定 this ,直接將this.handleChange 傳入 onChange,這時調用這個函數時, this將會是是undefined

綁定this的方法
  • 第一種:class 的方法使用箭頭函數, 如上例。

    handleChange = (event) => {
        const { name, value } = event.target;
        this.setState({
          [name]: value,
        });
      };
    
  • 第二種: 在構造函數中使用 this.handleChange.bind(this) 進行綁定。

    constructor() {
        super();
        this.handleChange = this.handleChange.bind(this);
      }
    handleChange(event) {
    const { name, value } = event.target;
    this.setState({
      [name]: value,
    });
    }
    
  • 第三種:其實也相當於第二種,只不過不使用構造函數

    handleChange(event) {
    const { name, value } = event.target;
    this.setState({
      [name]: value,
    });
    
    // 直接在傳入時進行綁定  
     onChange={this.handleChange.bind(this)}  
    

前面實現了表單的數據更新,最後一步是提交表單的數據並更新App組件的 state狀態。

修改App.js:

創建一個名爲handleSubmit 的函數,用來更新 state 的數據(添加表單提交的數據):

handleSubmit = (character) => {
    //  添加數據
    this.setState({
      characters: [...this.state.characters, character],
    });
  };

將其傳給 Form 組件:

render() {
    // 提取數據
    return (
      <div className="app">
        <h1> Hello, React</h1>
        <Table
          characterData={this.state.characters}
          removeCharacter={this.removeCharacter}
        />
        <Form handleSubmit={this.handleSubmit} />
      </div>
    );
  }

接下來,在 Form.js 中創建名爲 submitForm(),該方法將調用 父組件傳遞的方法 handleSubmit。將Form組件的 state作爲參數傳入:

修改 Form,js:

// 提交表單
  submitForm = () => {
    // 添加數據
    this.props.handleSubmit(this.state);
    // 添加之後清空state
    this.setState(this.initialState);
  };

最後,我們爲表單添加一個提交按鈕用來提交表單:

        <input type="button" value="submit" onClick={this.submitForm} />

⚠️注意:這裏的submitFrom方法也得綁定this

運行項目,具體的功能就實現了:
在這裏插入圖片描述

條件渲染

React 中的條件渲染和javaScript 中的一樣,可以使用 運算符 if 或者條件運算。

下面實現一個登錄/登出按鈕,根據不同的狀態現實不同的效果。爲了方便,還是在上面的項目實現這效果。

添加一個Login.js文件:

  • 創建兩個組件:LoginButtonLogoutButton 分別代表登錄和註銷。

    // 登錄
    const LoginButton = (props) => {
      return <button onClick={props.click}>Login</button>;
    };
    
    // 註銷
    const LogoutButton = (props) => {
      return <button onClick={props.click}>Logout</button>;
    };
    
  • 創建一個有狀態的組件LoginControl,用來根據當前的狀態來渲染上面的兩個組件。

class LoginControl extends Component {
  // 初始狀態
  state = {
    isLoggedIn: false,
  };
  
  handleLoginClick = () => {
    this.setState({
      isLoggedIn: true,
    });
  };
  handleLogoutClick = () => {
    this.setState({
      isLoggedIn: false,
    });
  };

  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    // 通過if 語句來進行條件渲染
    if (isLoggedIn) {
      button = <LoginButton click={this.handleLogoutClick} />;
    } else {
      button = <LogoutButton click={this.handleLoginClick} />;
    }
    return (
      <div>
        {button}
      </div>
    );
  }
}
  • 爲了更好的看到效果,我們每次切換後,加上對應的描述。創建兩個組件,代表切換後對應的描述:
// 登錄
const LoginGreeting = () => {
  return <span>Welcome </span>;
};
// 註銷
const LogoutGreeting = () => {
  return <span>Please sign up</span>;
};
  • 創建一個組件Geeting ,來渲染上面的描述組件.這裏使用三目運算符來實現
const Greeting = (props) => {
  const { isLoggedIn } = props;
  return <div>{isLoggedIn ? <LoginGreeting /> : <LogoutGreeting />}</div>;
};
  • 最後,在LoginControl 組件中加入這個 Greeting組件,同時在App 組件中加入LoginControl 組件。
// ...
return (
      <div>
        {button}
        <Greeting isLoggedIn={isLoggedIn} />
      </div>
  // ...

App加入LoginControl組件:

// ...
render() {
    // 提取數據
    return (
      <div className="app">
        <h1> Hello, React</h1>
        <Table
          characterData={this.state.characters}
          removeCharacter={this.removeCharacter}
        />
        <Form handleSubmit={this.handleSubmit} />
        <LoginControl />
      </div>
    );
  }
  // ...

運行項目(樣式有點醜~):

在這裏插入圖片描述

列表 & key

前面的例子 TableBody組件中 ,已經接觸了列表:

const TableBody = (props) => {
  const rows = props.characterData.map((item, index) => {
    return (
      <tr key={index}>
        <td>{item.name}</td>
        <td>{item.job}</td>
        <td>
          <button onClick={() => props.removeCharacter(index)}>Dalete</button>
        </td>
      </tr>
    );
  });
  return <tbody>{rows}</tbody>;
};

這個組件中,rowstr的數組,最後將rows 插入到 < tbody> 元素中。

這裏可以看到每一個 tr 都有一個特殊的屬性key,這個key是必要的(這與diff算法有關)。

key 幫助 React 識別哪些元素改變了,比如刪除或者添加。所以應當給數組中的每一個元素給予一個確定的標識。一個元素的key最好是這個元素在列表中擁有的獨一無二的字符串。通常使用數據的id來作爲元素的 key。如果元素沒有確定的 id時,可以使用元素索引 index 作爲 key

⚠️注意:

  • 元素的key只有放在就近的數組上下文中才有意義。如上例中的map 方法中的元素需要設置 key 屬性
  • key 值只是在兄弟節點間獨一無二,而不是全局的唯一。(也就是說,生成兩個不同的數組時,可以使用相同的key

生命週期

在這裏插入圖片描述

掛載

當組件實例被創建並插入 DOM中,其生命週期調用順序如下:

  • constructor: 構造函數初始化,最先被執行,初始化 state
  • getDerivedStateFromProps :這是一個靜態方法,需要在前面添加static屬性
  • render: 渲染函數,返回渲染的內容,當頁面更新時也會觸發
  • componentDidMount: 組件掛載之後,這個時候組件已經掛載完畢了
更新
  • getDerivedStateFromProps : 組件即將被更新,這裏參數分別對應前後被修改的內容,通過返回一個布爾值告知是否要更新視圖。
  • render: 當視圖更新,那麼render 也會更新
  • getSnapshotBeforeUpdate: getSnapshotBeforeUpdaterender之後componentDidUpdate之前輸出,類似於中間件用來做一些捕獲操作
  • componentDidUpdategetSnapshotBeforeUpdate,有三個參數prevPropsprevStatesnapshot,表示之前的 props,之前的 state,和snapshotsnapshotgetSnapshotBeforeUpdate返回的值

卸載

  • componentWillUnmount: 組件卸載,可以清除一些定時器,取消網絡請求。

修改 App.js 代碼:

 class App extends Component {
  constructor() {
    super();
    console.log('1: constructor初始化');
  }
  state = {
    characters: [
      {
        name: 'Charlie',
        job: 'Janitor',
      },
      {
        name: 'Mac',
        job: 'Bouncer',
      },
      {
        name: 'Dee',
        job: 'Aspring actress',
      },
    ],
  };

  // 移除項目
  removeCharacter = (i) => {
    this.setState({
      characters: this.state.characters.filter((item, index) => {
        return i !== index;
      }),
    });
  };

  handleSubmit = (character) => {
    //  添加數據
    this.setState({
      characters: [...this.state.characters, character],
    });
  };

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('getDerivedStateFromProps: 組件將要被更新');
    console.log(prevState);
    return true;
  }

  componentDidMount() {
    console.log('componentDidMount 組件掛載完畢');
  }

  getSnapshotBeforeUpdate(preProps, prevState) {
    console.log(preProps, prevState);
    return 'top: 200';
  }
  
  componentDidUpdate(preProps, prevState, snapshot) {
    console.log('組件更新完畢');
    console.log(preProps, prevState, snapshot);
  }
  render() {
    // 提取數據
    return (
      <div className="app">
        <h1> Hello, React</h1>
        <Table
          characterData={this.state.characters}
          removeCharacter={this.removeCharacter}
        />
        <Form handleSubmit={this.handleSubmit} />
        <LoginControl />
      </div>
    );
  }
}       

運行結果:
在這裏插入圖片描述

總結

學習React很多都是以Vue的思路先入個門,很多東西都是類似的。第一篇入門文章就到這裏,後面繼續~~。

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