如果你也剛入門React,來一起學習吧

zhangyuxiang1226發表於2018-09-05

本文提要

本文主要寫一些CRA腳手架的安裝,React的語法,元件分類和元件傳值等;如果您是已經在React上有豐富經驗的開發者,歡迎指出文中有問題和可以改進的地方,對此我將表示感謝!
這是react的官方站點React官網

本文將主要分為:

  • React專案搭建
  • React語法
  • React元件介紹
  • React元件傳值
  • 一個簡單的元件化小例子
  • 本文暫時不介紹React-Router

閱讀全文可能會花費您10-20分鐘,如果覺得有興趣,可以一起敲敲程式碼
(使用編輯器:VSCODE,外掛:VS Code ES7 React/Redux/React-Native/JS snippets,這個外掛可以快速構建元件格式,如果想要練練手的同學就不要用快速指令了哦)

下面開始正文

1.通過腳手架建立React專案

First thing first,這裡我們利用create-react-app(需要nodejs環境)來建立這個專案,畢竟比較方便嘛,有其他建立專案和服務的方式也可以使用。

如果你也剛入門React,來一起學習吧
找一個工作資料夾,然後開啟命令列工具,輸入create-react-app mycode就可以建立一個資料夾為mycode的專案資料夾,注意哦,這個專案名稱不支援大寫字母
ok,幾分鐘後會提示你腳手架初始化成功了

如果你也剛入門React,來一起學習吧
這裡有幾個指令:

  • npm start開啟開發伺服器,一般預設是3000埠,啟動後會自動彈出localhost:3000的頁面
  • npm run build為生產環境建立打包的靜態檔案
  • npm test開啟測試,這個我沒有用過,有用過的同學可以在評論裡分享一下使用技術文章
  • npm run ejectEject 將所有的工具(配置檔案和 package.json 依賴庫)解壓到應用所在的路徑,這個過程是不可逆的

那我們開始吧,cd mycode & npm start

2.基本語法

專案中兩個重要的檔案

如果你也剛入門React,來一起學習吧
我們啟動後會看到這個介面,這是腳手架自帶的。 請移步到編輯器,這裡我們暫時只關注兩個重要的檔案

  • public/index.html 因為React搭建的是SPA,所以index.html是我們的主頁,在檔案中你也可以看到<div id="root"></div>,root就是根元件渲染的位置。
  • src/index.js 這是我們的主要的js檔案,
    其中這一句表示式:render(<App></App>, window.root)表明,我們使用一個渲染方式render,將App渲染到root中去,不論App中有什麼,有多少層級,有多少元件,有多少邏輯,最終只有這一個入口。

React中的一切都從這裡開始

我們將腳手架src下的所有檔案全部刪掉,建立一個空白的index.js,開始coding。

index.js中,我們要做的就是,引入React庫,引入react-dom,引入根元件,然後執行根元件的渲染方法:

  • import React from 'react' 注意這裡的React必須首字母大寫
  • import {Component} from 'react',引入元件方法,使用{Component}解構方式引入
  • import App from './App',引入根元件App,我們再下一步將會建立一個src/App.js作為我們的根元件,這裡你可以取任何名字作為你的根元件js,我習慣取作App
  • render(<App></App>, window.root)渲染元件到root容器,render是react的核心渲染方法,後面我們會一直用到

語法介紹

① jsx簡介

按照上一節的引入各種庫,我們可以在index.js中coding來學習React的基礎語法。
簡要來說,react的核心語言是jsx,按照官方文件的舉例:

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

ReactDOM.render(
  element,
  document.getElementById('root')
);
複製程式碼

我們在React下寫的<h1><h1>這樣的html標籤,實際上都會按照上面的程式碼渲染到頁面上,只不過作為一個很sweet的語法糖,我們不需要再寫ReactDOM.render(),而是

let el = <h1>hello <span>world</span></h1>
render(el,window.root);
複製程式碼

使用javascript + xml語法,定義一個元素,然後再render渲染就可以了;在index.js中,你可以先註釋掉原本的render(<App></App>, window.root)這句話,改為上面的程式碼,在localhost:3000中可以看到

如果你也剛入門React,來一起學習吧
hello world渲染上了頁面;(後續如果有時間,會寫一篇小文講解一下react虛擬dom的實現原理)

② <>和{}

用一句話來概括就是: jsx元素/react元素 用<號標識, 看到{ 會認為裡面包含的是js程式碼

1){}中執行js

  • 變數取值
let str = '<h1>world</h1>'
let el = (
  <div>
    <div>{str}</div>
  </div>
)
render(el,window.root);
複製程式碼

頁面效果如圖

如果你也剛入門React,來一起學習吧

str變數在{}中執行,div中間的內容應該是字串'<h1>world</h1>',而不是標籤h1

  • 登出

如果我們想在程式碼中寫備註怎麼辦?是這樣嗎//,哦不行,加個{//}呢?哦不行,你在編輯器中可以看到

如果你也剛入門React,來一起學習吧
這後面的}也被登出了,所以在react中,我們使用 {/*hello*/}的方式作為備註

  • 字串解析為html
    在第一個小例子裡面提到 '<h1>world</h1>'會被作為字串內容渲染,但是如果確實想要作為dom展示h1呢?
    這裡我們使用 <div dangerouslySetInnerHTML={{ __html: str }}></div>,這個API很長對吧,我們在容器上標註 dangerouslySetInnerHTML~危險地設定innerHTML

    如果你也剛入門React,來一起學習吧
    這麼做可能有被插入惡意執行指令碼的的風險。

  • 執行一個方法

function a() { 
  return <h3>hello function</h3>
}

let el = (
  <div>
    <div>{a()}</div>
  </div>
)
複製程式碼

頁面:

如果你也剛入門React,來一起學習吧

  • 迴圈
    新增一個li key的要時最好不要用陣列的索引,為了能標示每個迴圈元素便於dom-diff, 一般用id,這裡由於例子比較簡單,我們使用陣列的索引填寫key ;使用arr.map的方式處理陣列返回
let arr = [1,2,3];
// 
let el = (
  arr.map((item, key) => (
    <li key={key}>{item}</li>
  ))
)
複製程式碼

如果你也剛入門React,來一起學習吧

  1. jsx中html屬性的幾個特點
  • classclassName 這個駝峰方式的寫法代替了原生html的class,但是class還是可以用的,腳手架會提示你這裡應當使用className

  • forhtmlFor 這個for是html的label上的for,用於指向控制的input,在jsx中我們使用htmlFor來替代

<label htmlFor="username">使用者名稱</label>
<input type="text" id="username" />
複製程式碼
  • <div style="color:red">hello</div><div style={{ color: 'red' }}>hello</div> 外層的{}表示js語法的標示,{ color: 'red' }是物件

  • React.Fragment
    如果有用過vue的同學應該知道,vue返回的html一定要有一個根節點包裹,即返回的dom一定是一個,不能是平級的多個,即

<div>
    <div></div>
    <p></p>
</div>
複製程式碼

在react中同樣,如果我們返回平級的多個div的話:

如果你也剛入門React,來一起學習吧
react會提示語法錯誤:jsx必須被一個閉合標籤包裹

但是如果我們在某種情況下,必須使用一些平級元素怎麼辦呢,比如處於樣式的考慮,我們外層沒有什麼需要div包裹的。這時候我們使用<React.Fragment>來包裹平級的元素,這個<React.Fragment>是沒有實際意義的,就充當一個節點閉合標籤。

let el1 = (
  <React.Fragment>
    <div>{str1}</div>
    <div>{a()}</div>
    <div>{JSON.stringify(obj)}</div>
    <div>{false?<span>你好</span>:void 0}</div>
  </React.Fragment>
)
複製程式碼

這樣就不會報錯了

總的來說,react的API較少,寫jsx是很自由的,js+xml的方式,使js功底很深厚的開發者可以在html中任意的書寫js邏輯,所寫即所得,可能這就是react的魅力吧。

3.元件

在react專案中,基本上所有的結構功能都可以拆分成很細的一個個元件,比如一個頁面上常用的選單欄,可以拆分成:列表框List,列表項ListItem,列表連結Link等等,這樣的好處是:
1.複用 2.方便維護 3.提高工作效率。

react宣告元件的方式分為函式宣告和類宣告

  • 函式式宣告元件
    函式式宣告元件的方式如下
function Build(props) {
  let {title,content} = props;
  return (
    <div>
      <div>{title}</div>
      <div>{content}</div>
    </div>
  )
}

render(<div>
  <Build title="build1" content="content1"></Build>
  <Build title="build2" content="content2"></Build>
  <Build title="build3" content="content3"></Build>
</div>, window.root);
複製程式碼

如果我們僅需要展示一些資訊到頁面上,不需要去控制變化,則函式元件可以簡單實現
元件的定義一定要是首字母大寫的,函式式元件傳值的方式是按照在元件中定義了屬性名,在元件使用時直接寫在元件上 <Build title="build3" content="content3"></Build>
函式元件的缺點是 1.沒有this 2.沒有狀態 3.沒有宣告週期 可以通過定時器可以實現函式式元件中值的定時改變,比如這個例子

function Clock(props) {
  return <div> 時間更新:<span>{props.time}</span></div>
}
setInterval(()=>{
  render(<Clock time={new Date().toLocaleString()} />, window.root);
},1000)
複製程式碼
  • 類宣告
    使用es6建立類的方式建立元件,類宣告的元件擁有了狀態,檢視通過setState方法進行更新 之後我們做的小例子的元件,都用類宣告的方式進行建立。在使用中來學習使用

受控元件 和 非受控元件

非受控元件:表單資料由DOM本身處理。即不受setState()的控制,與傳統的HTML表單輸入相似,input輸入值即顯示最新值(使用 ref 從DOM獲取表單值)
受控元件:在HTML中,標籤<input><textarea><select>的值的改變通常是根據使用者輸入進行更新。在React中,可變狀態通常儲存在元件的狀態屬性中,並且只能使用 setState() 更新,而呈現表單的React元件也控制著在後續使用者輸入時該表單中發生的情況,以這種由React控制的輸入表單元素而改變其值的方式,稱為:“受控元件”。

這裡我們寫一個非受控元件的小例子,我們輸入的值通過點選顯示出來;非受控元件常用於操作dom,較為方便

import React,{Component} from 'react';
import {render} from 'react-dom';
class UnControl extends Component{
  b=React.createRef();
  handleClick = () =>{
    alert(this.a.value); // 寫法1
    alert(this.b.current.value) // 寫法2
  }
  render(){
    return (<div>
      <input type="text" id="username" ref={dom=>this.a=dom}/>
      <input type="text" id="password" ref={this.b}/>
      <button onClick={this.handleClick}>點選</button>
    </div>)
  }
}
render(<UnControl></UnControl>, window.root);

複製程式碼

如果你也剛入門React,來一起學習吧

如果你也剛入門React,來一起學習吧

接下來我們將實現這樣的一個小例子:

如果你也剛入門React,來一起學習吧

實現一個評論元件,類似於掘金下方的評論欄,我們將這個元件大功能拆分為
根元件App,列表元件List,列表項ListItemComment評論元件,在實現的過程中,我們會討論元件間資料傳遞的方式。

4.更新檢視的方法

首先,我們不拆分元件,將上述的例子簡單構建出來,頁面結構使用bootstrap UI(npm install boostrap@3) 元件。
在這個例子中,我們採用axios(npm install axios)請求初始列表資料,封裝為一個request.js,程式碼如下:

import axios from 'axios';

axios.interceptors.response.use(function (res) {
  if (res.data.code === 0) {
    return res.data.users
  } else {
    return Promise.reject('錯誤');
  }
})

export default axios
複製程式碼

請求的資料格式自己簡單擬定為:

{
    "code":0,
    "users": [
      {
        "id": 1,
        "avatar": "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg",
        "username": "Jim",
        "content": "Hi,你的文章很不錯"
      },
      {
        "id": 2,
        "avatar": "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg",
        "username": "Jim",
        "content": "一般般的說"
      }
    ]
  }
複製程式碼

然後貼出我們的App.js,我們將全部的內容都放在App.js中,不拆分元件:

import React, { Component } from 'react';
import axios from './request'
import 'bootstrap/dist/css/bootstrap.css'
import './Common/common.css'

class App extends Component {
    state = {
        users: [],
        count: 0,
        id: 3
    }
    // 點贊功能
    increment = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    // 新增評論
    addComment = (val) => {
        let id = this.state.id;
        let users = [...this.state.users, { avatar:  "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg", content: val, username: 'Jim', id: id}];
        this.setState({
            users
        });
        this.state.id+=1;
    }
    content = React.createRef();
    // 提交資料
    handleSubmit = (e) => {
      e.preventDefault();
      this.addComment(this.content.current.value);
  }
    // 刪除一條
    removeById = (id) => {
        let users = this.state.users.filter(user=>user.id!==id); // 排除列表裡相同id的,即達到刪除的目的
        this.setState({
            users
        })
    }
    // 獲取列表資料
    async componentDidMount() {
        let users = await axios.get('/users.json');
        this.setState({
            users
        });
    }
    render() {
        return (
          <div className="container">
          <div className="panel panel-danger">
              <div className="panel-heading">
                  評論
              </div>
              <div className="panel-body">
              {
                this.state.users.map((user, index) => {
                  return (
                    <div className="media">
                    <div className="media-left">
                        <img className="avatar" src={user.avatar} />
                    </div>
                    <div className="media-right">
                        <h3>{user.username} </h3>
                        <div>評論:{user.content}</div>
                        <button className="btn btn-danger" onClick={(e)=>{
                            this.removeById(user.id)
                        }}>刪除</button>

                    </div>
                </div>
                  )
                })
              }
              
              </div>
              <div className="panel-bottom">
                <form onSubmit={this.handleSubmit}>
                <textarea className="form-control" required ref={this.content}></textarea>
                <button type="submit" >評論</button>
                </form>
              </div>
          </div>
      </div>
            
        );
    }
}

export default App;
複製程式碼

效果:

如果你也剛入門React,來一起學習吧
到這裡,我們的程式碼實現的功能有,加一條評論,也可以刪除一條評論。
在React中,檢視是受到資料的驅動的,我們最初定義的

 state = {
        users: [],
        count: 0,
        id: 3
    }
複製程式碼

state中users的資料,會在componentDidMount生命週期時,獲取到users列表,並通過this.setState({ users });方法更新檢視。其他的操作,類似於handleSubmitremoveById同樣都是通過操作state.users的資料達到增刪的目的。

5.拆分元件

考慮到一個專案中的複雜度,我們可以將上述App.js中的相關內容進行拆分為:列表元件List,列表項ListItemComment評論元件,這樣,構造其他結構的時候,我們就不用再去重新寫一遍相同的程式碼。我們在src資料夾下新建components資料夾,並且建立List.js ListItem.js Comment.js

  • 列表項元件:
import React, { Component } from 'react'
export default class ListItem extends Component {
    state = {
        users: [],
        id: 100000
    }
    addComment = (val) => {
        let id = this.state.id;
        let users = [...this.state.users, { avatar:  "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg", content: val, username: 'Jim', id: id}];
        this.setState({
            users
        });
        this.state.id+=1;
    }
    handleClick = (id) => {
        this.props.removeById(id);
    }
    removeById = (id) => {
        let users = this.state.users.filter(user=>user.id!==id); // 排除列表裡相同id的,即達到刪除的目的
        this.setState({
            users
        })
    }
  render() {
    let {id, avatar, content, username} = this.props;
    return (
        <div className="media">
                    <div className="media-left">
                        <img className="avatar" src={avatar} />
                    </div>
                    <div className="media-right">
                        <h3>{username} {id}</h3>
                        <div>評論:{content}</div>
                        <button className="btn btn-danger" onClick={(e)=>{
                            this.handleClick(id)
                        }}>刪除</button>

                    </div>
                </div>
       
    )
  }
}

複製程式碼
  • 列表元件
import React, { Component } from 'react'
import ListItem from './ListItem'
export default class List extends Component {
  static props = {
    showComment: true
  }
  render() {
    return (
      <div>
        {
            this.props.users.map((user, index) => {
                return (
                    <ListItem showComment={this.props.showComment} {...user} key={index} removeById={this.props.removeById} addComment={this.props.addComment}></ListItem>
                )
            })
        }
      </div>
    )
  }
}

複製程式碼
  • 評論框元件
import React, { Component } from 'react'

export default class Comment extends Component {
    content = React.createRef();
    handleSubmit = (e) => {
        e.preventDefault();
        this.props.addComment(this.content.current.value);
    }
    render() {
        return (
            <form onSubmit={this.handleSubmit}>
            <textarea className="form-control" required ref={this.content}></textarea>
            <button type="submit" >評論</button>
            </form>
        )
    }
}

複製程式碼
  • App.js變為
import React, { Component } from 'react';
import axios from './request'
import 'bootstrap/dist/css/bootstrap.css'
import './Common/common.css'
import Comment from './components/Comment'
import List from './components/List'
import {Provider} from './context'

class App extends Component {
    state = {
        users: [],
        count: 0,
        id: 3
    }
    increment = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    addComment = (val) => {
        let id = this.state.id;
        let users = [...this.state.users, { avatar:  "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg", content: val, username: 'Jim', id: id}];
        this.setState({
            users
        });
        this.state.id+=1;
    }
    removeById = (id) => {
        console.log(id)

        let users = this.state.users.filter(user=>user.id!==id); // 排除列表裡相同id的,即達到刪除的目的
        this.setState({
            users
        })
    }
    async componentDidMount() {
        let users = await axios.get('/users.json');
        this.setState({
            users
        });
    }
    render() {
        return (
        <Provider value={{increment: this.increment}}>
            <div className="container">
                <div className="panel panel-danger">
                    <div className="panel-heading">
                        評論
                    </div>
                    <div className="panel-body">
                        <List users={this.state.users} showComment={true} removeById={this.removeById} addComment={this.addComment}></List>
                    </div>
                    <div className="panel-bottom">
                    
                    <br/>
                    <Comment addComment={this.addComment}></Comment>
                    獲得的贊數量{this.state.count}
                    </div>
                </div>
            </div>
        </Provider>
            
        );
    }
}

export default App;
複製程式碼

看到這裡,一定有疑問,那麼我們之前定義的users資料,removeByIdaddComment的方法,怎麼用到元件上呢?下面我們進行講解。

6.元件間屬性的傳遞

  • 元件的資料互動的方式是屬性傳遞,傳遞屬性值或方法
  • 子元件不能直接修改屬性值
  • 但是可以通過父元件傳遞進來的方法呼叫以改變屬性值
  • 資料傳遞是單向的:父->子,即常說的單項資料流
  • 子元件獲取屬性的方法:`this.props.fn
  • 可以使用contextApi實現跨元件傳遞

上一節我們拆分的元件中,在列表元件中原本的迴圈體資料來源,由this.state.users改為了使用this.props.users,而在App.js中傳入的方式為

<List users={this.state.users} showComment={true} removeById={this.removeById} addComment={this.addComment}></List>
複製程式碼

傳入和獲取是一一對應的。
同樣,由於ListItem元件需要removeById方法,所以我們從App.jsList元件就傳入removeById,在List元件中呼叫ListItem時,再次傳入ListItem,是一個父傳子,子傳孫的過程:

<ListItem showComment={this.props.showComment} {...user} key={index} removeById={this.props.removeById} addComment={this.props.addComment}></ListItem>
複製程式碼

ListItem元件中,我們對removeById方法再包裝一層

handleClick = (id) => {
        this.props.removeById(id);
}

...

<button className="btn btn-danger" onClick={(e)=>{
    this.removeById(user.id)
}}>刪除</button>

複製程式碼

這裡我們的刪除方法來自於根元件傳遞下來的方法,子元件獲取後,對同樣是傳遞進來的users進行修改,以到達改變資料的目的。以上就是簡單的元件傳值的講解。

contextApi

如果我們想給這個列表加一個點贊功能,即任何一個列表項元件都可以點贊,而且點贊還可以收集總數,這時候如果再去用父子間元件傳值,可能程式碼實現起來會比較麻煩或者易錯,因為涉及的層級很多。所以我們利用contextApi來實現(react16.3)。

引入的方式(在例子中,我抽離了這個引入到context.js,就不用在每個頁面寫一遍解構了):

import React from 'react'
let {Provider, Consumer} = React.createContext();

export {Provider, Consumer}
複製程式碼

在元件中的使用方法是,在父元件引入後,將父元件的返回值使用Provider包裹,並傳入value屬性:

import React, { Component } from 'react';
import {Provider} from './context'

class App extends Component {
    state = {
        users: [],
        count: 0,
        id: 3
    }
    // 點贊功能
    increment = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    render() {
        return (
        <Provider value={{increment: this.increment}}>
            <div className="container">
                <div className="panel panel-danger">
                    <div className="panel-heading">
                        評論
                    </div>
                    <div className="panel-body">
                        <List users={this.state.users} showComment={true} removeById={this.removeById} addComment={this.addComment}></List>
                    </div>
                    <div className="panel-bottom">
                    
                    <br/>
                    <Comment addComment={this.addComment}></Comment>
                    獲得的贊數量{this.state.count}
                    </div>
                </div>
            </div>
        </Provider>
            
        );
    }
}

export default App;
複製程式碼

在子元件中,需要使用(消費)的返回值外層包裹Consumer,使用箭頭函式傳入value的值,即Provider傳入的屬性,即可在元件中直接呼叫父元件或更高階的元件的傳入屬性。

import React, { Component } from 'react'
import {Consumer} from '../context'
...
export default class ListItem extends Component {
    ...
  render() {
    let {id, avatar, content, username} = this.props;
    return (
        <Consumer>
            {(value)=>{
                return <div className="media">
                    <div className="media-right">
                        ...
                        <button className="btn btn-primary" onClick={()=>{
                            value.increment()
                        }}>贊</button>
                        ...
                      </div>
                </div>
            }}
            
        </Consumer>
       
    )
  }
}

複製程式碼

總結

以上是我學習React入門的一些小總結,寫了一個不太成熟的例子來練手,在表述上可能有一些跳躍還請見諒。這裡附上這個小例子的Github程式碼,有需要詳細瞭解的同學可以看看:code
希望我的文章能幫到你。

相關文章