若是你也剛入門React,來一塊兒學習吧

本文提要

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

本文將主要分爲:css

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

閱讀全文可能會花費您10-20分鐘,若是以爲有興趣,能夠一塊兒敲敲代碼
(使用編輯器:VSCODE,插件:VS Code ES7 React/Redux/React-Native/JS snippets,這個插件能夠快速構建組件格式,若是想要練練手的同窗就不要用快速指令了哦)html

下面開始正文

1.經過腳手架建立React項目

First thing first,這裏咱們利用create-react-app(須要nodejs環境)來建立這個項目,畢竟比較方便嘛,有其餘建立項目和服務的方式也可使用。vue

找一個工做文件夾,而後打開命令行工具,輸入 create-react-app mycode就能夠建立一個文件夾爲 mycode的項目文件夾,注意哦,這個項目名稱不支持大寫字母
ok,幾分鐘後會提示你腳手架初始化成功了

這裏有幾個指令:

  • npm start開啓開發服務器,通常默認是3000端口,啓動後會自動彈出localhost:3000的頁面
  • npm run build爲生產環境建立打包的靜態文件
  • npm test開啓測試,這個我沒有用過,有用過的同窗能夠在評論裏分享一下使用技術文章
  • npm run ejectEject 將全部的工具(配置文件和 package.json 依賴庫)解壓到應用所在的路徑,這個過程是不可逆的

那咱們開始吧,cd mycode & npm startjava

2.基本語法

項目中兩個重要的文件

咱們啓動後會看到這個界面,這是腳手架自帶的。 請移步到編輯器,這裏咱們暫時只關注兩個重要的文件

  • 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。node

index.js中,咱們要作的就是,引入React庫,引入react-dom,引入根組件,而後執行根組件的渲染方法:react

  • 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,按照官方文檔的舉例:ios

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

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

咱們在React下寫的<h1><h1>這樣的html標籤,實際上都會按照上面的代碼渲染到頁面上,只不過做爲一個很sweet的語法糖,咱們不須要再寫ReactDOM.render(),而是git

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

使用javascript + xml語法,定義一個元素,而後再render渲染就能夠了;在index.js中,你能夠先註釋掉本來的render(<App></App>, window.root)這句話,改成上面的代碼,在localhost:3000中能夠看到es6

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);
複製代碼

頁面效果如圖

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

  • 註銷

若是咱們想在代碼中寫備註怎麼辦?是這樣嗎//,哦不行,加個{//}呢?哦不行,你在編輯器中能夠看到

這後面的}也被註銷了,因此在react中,咱們使用 {/*hello*/}的方式做爲備註

  • 字符串解析爲html
    在第一個小例子裏面提到 '<h1>world</h1>'會被做爲字符串內容渲染,可是若是確實想要做爲dom展現h1呢?
    這裏咱們使用 <div dangerouslySetInnerHTML={{ __html: str }}></div>,這個API很長對吧,咱們在容器上標註 dangerouslySetInnerHTML~危險地設置innerHTML

    這麼作可能有被插入惡意執行腳本的的風險。

  • 執行一個方法

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

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

頁面:

  • 循環
    添加一個li key的要時最好不要用數組的索引,爲了能標示每一個循環元素便於dom-diff, 通常用id,這裏因爲例子比較簡單,咱們使用數組的索引填寫key ;使用arr.map的方式處理數組返回
let arr = [1,2,3];
// 
let el = (
  arr.map((item, key) => (
    <li key={key}>{item}</li>
  ))
)
複製代碼

  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會提示語法錯誤: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);

複製代碼

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

實現一個評論組件,相似於掘金下方的評論欄,咱們將這個組件大功能拆分爲
根組件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中,視圖是受到數據的驅動的,咱們最初定義的

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。 但願個人文章能幫到你。

相關文章
相關標籤/搜索