React入門

React是一個JavaScript框架,用它可讓咱們更便捷高效地進行前端開發。由於是入門,因此本文是以在html文件中引入js的方式進行練習的。若是想要直接建立並啓動一個react項目(須要先安裝好node和npm),可使用命令:javascript

npx create-react-app my-app
 cd my-app
 npm start
複製代碼

官方文檔地址:React。由於是React入門,因此本文不涉及ReduxReact Routercss

準備工做

E6知識

首先須要準備ES6知識,尤爲是class。(熟悉ES6的話能夠跳過這部分)數組解構,對象解構等也須要了解。ES6的學習能夠參考ES6入門。在此只簡單說一下類相關的內容:html

// let container = new Container(100); // 類的聲明不會提高,在此處使用Container會報錯ReferenceError

// 類的聲明
class Container {
  // 構造函數constructor用於建立和初始化Container的實例對象
  constructor ( totalCapacity ) {
    this.totalCapacity = totalCapacity;

    this.usedCapacity = 0;
    this.contained = [];

    // class中的方法不會自動綁定到實例對象,因此須要咱們手動綁定
    this.add = this.add.bind(this);
    this.remove = this.remove.bind(this);
    this.printContained = this.printContained.bind(this);
  }
  // 靜態方法,調用方式Container.containerInfo(),靜態方法會被子類繼承
  static containerInfo () {
    console.log('這個是靜態方法呀\n');
  }
  // add、remove、printContained是定義在Container的prototype上的,能夠被子類繼承
  add (something, volume) { // 將某物加入容器
    const containedItem = {
      name: something,
      volume
    };
    let used = this.usedCapacity + volume;
    if (used > this.totalCapacity) {
      console.log(`此容器不能放下體積爲${volume}的物品`);
      return;
    }
    this.contained.push(containedItem);
    this.usedCapacity = used;
  }
  remove (something, volume) { // 將某物移出容器
    let containedLen = this.contained.length;
    if (containedLen === 0) return;
    const index = this.contained.findIndex((item) => item.name === something);
    if (index < 0) return;
    const item = this.contained[index];
    const diff = item.volume - volume;
    switch (true) {
      case diff === 0:
        this.contained.splice(index, 1);
        break;
      case diff > 0:
        this.contained[index] = {
          name: item.name,
          volume: diff
        };
        break;
      case diff < 0:
        console.log(`容器中的${something}體積不足${volume}\n`);
        break;
    }
  }
  printContained () {
    this.contained.forEach((item) => {
      const { name, volume } = item;
      console.log(`${name} ${volume}`);
    })
  }
}

let container = new Container(100); // 使用class定義的類必須用new調用,直接執行Container()會報TypeError

container.add('蘋果', 20);
container.printContained();
container.remove('蘋果', 10);
container.printContained();

// 若是沒有在constructor中手動給printContained綁定this,那麼執行如下兩行代碼就會報錯
// 由於printContained執行時的this是undefined,不是container(Container的實例)。
// const { printContained } = container;
// printContained();

// 靜態方法的調用方式
// Container.containerInfo();

// 類的繼承
class Box extends Container { // Box會繼承Container原型鏈上的方法
  constructor (totalCapacity, material) {
    // super方法調用父類的構造函數,將父類屬性添加到子類上。至關於Container.constructor.call(this, 參數1, 參數2...);
    super(totalCapacity);
    this.material = material; // Box類的屬性
    this.printContained = this.printContained.bind(this);
  }
  printContained () {
    console.log(`箱子是${this.material}制的`);
    console.log('箱子中包含:');
    super.printContained(); // 可使用super對象使用父類的方法,函數執行的過程當中this是指向Box實例的
  }
}

let box = new Box(100, '木頭');
box.add('芒果', 20);
box.add('西瓜', 10);
box.add('荔枝', 30);
box.printContained();
box.remove('芒果', 10);
box.printContained();

複製代碼

由於這個🌰主要是爲了說明類,因此基本上只有類相關的註釋。前端

JSX

JSX是在React中使用的一種語法,如下代碼對JSX進行了簡單的說明:java

let test = () => 1;
let divStyle = { // 注意這裏不是直接寫的style樣式,而是一個樣式對象,樣式屬性採用的是駝峯的命名方式
  backgroundColor: '#cfefe7',
  fontSize: '15px',
  textDecoration: 'underline'
};
let element = (
  <div> {/* ()裏面只能有一個元素 */} <h1>JSX</h1> <div style={divStyle} > {/* 這是內聯樣式的添加方法 */} <p>這就是使用JSX語法建立的一個「元素」,它既不是HTML也不是字符串。</p> <p>能夠經過{}包含任何JS表達式。好比:{test() + 1}。而且JSX自己也是表達式。</p> { test() > 0 && <p>當test()的值大於0的時候會展現這部分的內容</p> } <p>裏面不光能使用原生元素好比div等,還能包含在React中的自定義組件。</p> <p className="nothing">JSX語法中元素的屬性採用駝峯的命名方式。</p> </div> </div>
);
複製代碼

更多JSX相關內容,請查看官方文檔:JSXnode

其實使用ES6和JSX在React中不是必須的,官方提供了相應的用法:不使用ES6不使用JSX。本文仍是會在React中用ES6和JSX,由於使用ES6和JSX的代碼更加簡潔和直觀。react

React開發者工具

使用開發者工具可以方便查看組件以及組件的props,state。谷歌瀏覽器React開發者工具地址:React Developer Toolsgit

簡單的環境

我在html中直接引入js文件,而後在瀏覽器中打開html文件的時候報跨域錯誤了:es6

Access to script at 'file:///Users/.../practice/00.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.github

因此我起了一個簡單的node服務,在本地服務器上打開html件。

首先建立文件夾practice,在文件夾中建立server.jspractice.htmlpractice.jspractice.css文件。

server.js用Node.js建立了一個簡單的本地服務器,文件內容以下:

const http = require('http');
const fs = require('fs');
const url = require('url');

const server = http.createServer(function (req, res) {
  let pathname = url.parse(req.url).pathname;
  fs.readFile(pathname.substr(1), function(err, data){
    if (err) {
      res.writeHead(404, {'Content-Type': 'text/html'});
    } else {
      const index = pathname.lastIndexOf('.');
      const suffix = pathname.slice(index + 1);
      res.writeHead(200, {'Content-Type': `text/${suffix}`});
      res.write(data.toString());
    }
    res.end();
  });
});

server.listen(8080, function () {
  console.log(`server is running at http://127.0.0.1:8080`);
});
複製代碼

practice.html文件內容以下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <link rel="stylesheet" type="text/css" href="practice.css" />
</head>
<body>
  <div id="root"></div>

  <!-- 這裏引用了react和react-dom以及babel的壓縮版 -->
  <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  <script type="text/babel" src="practice.js"></script>
</body>
</html>

複製代碼

practice.js 中內容以下:

// ReactDOM.render方法將React元素渲染到一個DOM元素中
ReactDOM.render(
  <h1>React學習</h1>, 
  document.getElementById('root') 
);
複製代碼

pracitice.css 中內容以下:

#root h1 {
  color: green;
}
複製代碼

在practice目錄下執行命令node server 啓動服務。而後訪問地址http://127.0.0.1:8080/practice.html就能看到頁面中的綠色的"React學習"這幾個字。

開始學習React

元素

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

React元素和咱們已經熟悉的html元素的元素名是同樣的,例如divp等,可是React元素是一個普通的JavaScript對象,而html元素是真實的DOM節點。二者的使用方式也不一樣,React元素的屬性名稱以及事件綁定都是使用駝峯的命名方式,React元素還能在{}中使用任何表達式,React表單元素的使用也與html表單元素不一樣。

React元素:

<div className="card" onClick={test} style={ {color: 'green', fontSize: '12px'} }>點擊</div>
<input type="text" value={this.state.inputValue} onChange={this.handleChange} />
複製代碼

在以上代碼中,this.state.inputValue的值就是輸入框的值,當設置state中的inputValue值的時候,輸入框的值也會改變。state是組件中存放數據的地方,在下文的組件部分會進行說明。

想要將一個 React 元素渲染到根 DOM 節點中,只需把它們一塊兒傳入 ReactDOM.render()

例如上文中的這段代碼:

// ReactDOM.render方法將React元素渲染到一個DOM元素中
ReactDOM.render(
  <h1>React學習</h1>, // 這是React元素,是一個普通的對象
  document.getElementById('root') // 這是一個DOM象
);
複製代碼

html元素:

<div class="card" onclick="test()" style="color: green; font-size: 12px;">點擊</div>
<input type="text" name="userName" value="RenMo" onchange="handleChange(this.value)" />
複製代碼

組件

組件容許你將 UI 拆分爲獨立可複用的代碼片斷,並對每一個片斷進行獨立構思。

  • 組件能夠經過函數建立,也能夠經過類建立。
  • 組件的名稱必須是首字母大寫。
  • 經過props獲取組件的屬性。
  • 經過state放置組件的數據,經過setState設置組件的數據。

經過函數建立組件

將上文建立的practice.js 文件的內容替換以下:

function ShowText(props) {
  const { style, content } = props;
  return <p style={style}>{content}</p>;
}
const element = <ShowText content="React學習" style={ {color: 'green', fontSize: '30px'} }/>; ReactDOM.render( element, document.getElementById('root') ); 複製代碼

打開http://127.0.0.1:8080/practice.html,就能看見綠色的「React學習」幾個字。

ShowText是一個經過函數方式建立的組件,將它渲染到頁面中一樣須要使用ReactDOM.render函數。函數的參數props中包含了所定義的組件的屬性。組件的props是隻讀的。

函數定義的組件,若是內容有須要改變的,那麼必須從新調用一遍ReactDOM.render,好比要改變content的內容,就必須定義一個新的元素並調用ReactDOM.render進行渲染:

const element = <ShowText content="React學習測試測試" style={ {color: 'green', fontSize: '30px'} }/>; ReactDOM.render( element, document.getElementById('root') ); 複製代碼

從官方文檔中瞭解到,要在不使用class的狀況下使用Reactstate等特性,須要使用HookHook會在下文中提到。

經過類建立組件

將上文建立的practice.js 文件的內容替換以下(一個簡單的待辦清單的例子):

class TodoItem extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      complete: false
    };
    // 組件中定義的方法必須手動綁定this
    this.changeTodoState = this.changeTodoState.bind(this);
  }
  componentDidMount() { // 生命週期方法 ------組件第一次渲染到DOM中會觸發
    this.timer = null; // 爲了展現任務選中的效果,設置一個timer
  }
  componentDidUpdate () { // 生命週期方法 ------組件更新的時候會觸發
    console.log('組件更新了');
  }
  componentWillUnmount() { // 生命週期方法 ------組件從DOM中刪除的時候會觸發
    clearTimeout(this.timer);
  }
  // 若是在輸入框的點擊事件中既要傳入參數,又要使用event,那麼把event放在參數的最後一個位置便可
  changeTodoState (content, event) {
    let value = event.target.value;
    this.timer = setTimeout(() => {
      if (value) {
        // 經過setState改變狀態的值
        this.setState({
          complete: true
        });
        // setCompleteItems是從父組件傳過來的方法,調用這個方法能將子組件中的內容傳入父組件中
        // 也就是待辦清單的某一項完成後,將該清單的content傳入父組件中
        this.props.setCompleteItems(content);
      }
    }, 200);
  }
  render () {
    let complete = this.state.complete;
    let content = this.props.content;
    // 當complete完成的時候,返回的內容就是false,而不是組件的內容,因此清單的某一項就會隱藏
    return (
      (!complete) && 
      <li className="todo-item" key={content.toString()}>
        {/* 若是不須要綁定參數,在{}中直接寫this.changeTodoState就行,若是要傳入參數,則須要使用bind方法 */}
        <input type="checkbox" onChange={this.changeTodoState.bind(this, content)}/>
        <span>{content}</span>
      </li>
    );
  }
}
class TodoList extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      completeItems: []
    };
    this.setCompleteItems = this.setCompleteItems.bind(this);
  }
  // 這個方法是爲了將子組件中的內容傳到父組件中,item就是從子組件傳過來的內容
  setCompleteItems (item) {
    let completeItems = this.state.completeItems;
    completeItems.push(item);
    this.setState({
      completeItems
    });
  }
  render () {
    let list = this.props.list;
    let todoList = list.map((item) => <TodoItem content={item} setCompleteItems={this.setCompleteItems}/>);
    return (
      <div>
        <ul className="todo-list">
          {todoList}
        </ul>
        <p>已完成: {this.state.completeItems.join(',')}</p>
      </div>
    )
  }
}
let list = ['洗衣服', '買水果', '追劇'];
const todoList = <TodoList  list={list} />;
ReactDOM.render(
  todoList,
  document.getElementById('root')
);
複製代碼

其中props是組件的屬性,是不可變的。state是組件中的數據,能夠經過setState進行改變。

定義的事件須要在constructor中手動綁定this,不然就不能在組件中使用。

繼承自React.Component的類ShowText中包含一個render方法,每次組件更新render方法都會被調用,返回的內容能夠查看render方法官網地址,上面的練習例子中,返回的內容是一個React元素。

componentDidMountcomponentWillUnmountcomponentDidUpdate是經常使用的React的生命週期函數,分別在React組件的掛載、卸載、更新時被調用。還有其餘的生命週期鉤子函數:詳情可看React生命週期

若是要從子組件中改變父組件的內容,能夠像上面那個例子中同樣,在父組件中定義一個方法,上例的方法是setCompleteItems,將其做爲某屬性的值傳入子組件中,在子組件中經過this.props獲取到父組件傳來的方法,再根據須要作出相應的修改。

Hook

經過上文能夠了解到若是要使用statesetState等,就必須用類的方式定義組件,這樣會致使組件的狀態邏輯等有冗餘而且可能比較複雜,使用hook就是爲了提供更直接簡單的方法來進行開發。hook能在不使用class的狀況下使用ReactReact從16.8.0版本開始支持hook

接下來就使用hook的方法來實現上文中的待辦清單的例子,仍是將practice.js的內容替換以下:

// 在項目中可使用 import { useState } from 'react'; 引入useState
// 由於在瀏覽器中會將import轉換爲require,因而會報錯require is not defined,因此使用下面這個句子引入useState
let useState = React.useState; 
let useEffect = React.useEffect; 

function TodoItem (props) {
  const { content, setCompleteItems } = props;
  // 聲明一個名爲complete的state變量,能夠經過setComplete改變變量的值,complete的初始值爲false。
  const [ complete, setComplete ] = useState(false);
  let timer = null; // 定時器
  // useEffect的參數爲一個函數,這個函數在組件掛載和更新時都會執行
  useEffect(() => {
    // 在useEffect中返回一個函數,這個函數會在組件清除的時候執行
    return () => {
      clearTimeout(timer)
    }
  });
  function changeTodoState (content, event) {
    let value = event.target.value;
    timer = setTimeout(() => {
      if (value) {
        // 經過setComplete改變complete的值
        setComplete(true);
        setCompleteItems(content); // 這裏本來是this.props.setCompleteItems(content);
      }
    }, 200);
  }
  return (
    (!complete) && 
    <li class="todo-item" key={content.toString()}>
      {/* 若是不須要綁定參數,在{}中直接寫this.changeTodoState就行,若是要傳入參數,則須要使用bind方法 */}
      <input type="checkbox" onChange={changeTodoState.bind(this, content)}/>
      <span>{content}</span>
    </li>
  );
}

function TodoList (props) {
  const { list } = props;
  // 這裏一開始定的completeItems的數據格式是數組,不止爲什麼使用setCompleteItemsState的時候,completeItems的值也變了,可是組件中的completeItems仍然爲空
  // 因此就換成字符串的格式了
  const [ completeItems, setCompleteItemsState ] = useState('');
  const setCompleteItems = (completedItem) => {
    // 使用useState設置completeItems的值爲數組沒有生效,因而換成使用字符串,還須要手動組合一下字符串
    let completeItemsText = completeItems;
    completeItemsText = completeItems + (completeItemsText ? ',' + completedItem : completedItem);
    setCompleteItemsState(completeItemsText);
  };
  let todoList = list.map((item) => <TodoItem content={item} setCompleteItems={setCompleteItems}/>);
  return (
    <div>
      <ul class="todo-list">
        {todoList}
      </ul>
      <p>已完成: {completeItems}</p>
   </div>
  )
}
let list = ['洗衣服', '買水果', '追劇'];
const todoList = <TodoList  list={list} />;
ReactDOM.render(
  todoList,
  document.getElementById('root')
);
複製代碼

能夠看見使用hook以後,就不用像在類中那樣,須要使用this.state.varName,直接使用varName就能夠了。

(!complete) && 
  <li class="todo-item" key={content.toString()}> ... 複製代碼

state hook用於處理數據,對於effect hook,直接引用官方文檔的說明:

useEffect 就是一個 Effect Hook,給函數組件增長了操做反作用的能力。它跟 class 組件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具備相同的用途,只不過被合併成了一個 API。

在使用useEffect的時候,若是須要在組件卸載的時候進行一些處理,那麼在useEffect返回一個函數,在清除effect的時候,就會執行返回的函數的內容。好比如下代碼,在組件卸載的時候,會將文檔的標題改成''測試''。(這部分有點疑惑,雖然能夠看見文檔的標題在頁面卸載前是沒有變化的,可是在return的函數中使用console.log打印內容是可以打印出來的)。

useEffect(() => {
    // 更新文檔的title
    document.title = '123';
    return () => {
      document.title = `測試`;
    }
  });
複製代碼

useStateuseEffectuseContext是幾個基礎的hook,也能夠自定義hook

最後

代碼練習的最後結果是這樣的:源碼地址,使用DownGit可以拿到git倉庫中某文件夾的代碼。

上文中有提到,在React入門的過程當中有兩處疑問,可能須要進一步瞭解React以後才能找到其緣由,這篇React入門就寫到這裏了。

後天就是中華人民共和國成立70週年的日子,提早祝賀祖國母親生日快樂ヾ(≧▽≦*)o o(*≧▽≦)ツ。

相關文章
相關標籤/搜索