19-React-02 React 之高級使用

前言

  • React 中使用了 JSX 語法糖,是一種可以將 HTML 和 JS 揉著寫的語法糖;
  • 瀏覽器不能直接運行 JSX 語法糖,需要使用 babel 來翻譯;
  • 如果使用了 babel,就可以寫 ES6 代碼了;
  • 寫一點翻譯一點非常不方便,所以要用 webpack 結(jié)合 babel-loader 來 watch 文件;
  • 既然使用了 webpack,那我們就可以進(jìn)行模塊化開發(fā)了。

創(chuàng)建項目

  • 創(chuàng)建一個項目文件夾,在這個文件夾中配置 webpack + babel 環(huán)境,讓 webpack 可以指導(dǎo) babel 翻譯 ES6 語法:

    • 創(chuàng)建 package.json:cnpm init;
  • 安裝 webpack,并且設(shè)置為項目依賴:

    • cnpm i --save-dev webpack;需要在全局環(huán)境中已經(jīng)安裝好 webpack。
  • 創(chuàng)建 webpack.config.js 文件:參照官網(wǎng):https://webpack.js.org/configuration/

const path = require('path');

module.exports = {
    entry: "./app/main.js", // string | object | array

    output: {
        // options related to how webpack emits results

        path: path.resolve(__dirname, "dist"), // string
        // the target directory for all output files
        // must be an absolute path (use the Node.js path module)

        filename: "all.js", // string
    }
}
創(chuàng)建完對應(yīng)的項目文件之后,在控制臺直接執(zhí)行 webpack,就可以看到 all.js 文件。
到目前,我們已經(jīng)可以進(jìn)行標(biāo)準(zhǔn)的 CMD 模塊化開發(fā)了。
  • 我們再引入 babel-loader 來翻譯 ES6,然后修改 webpack.config.js 中的內(nèi)容。
const path = require('path');

module.exports = {
    entry: "./app/main.js", // string | object | array

    output: {
        // options related to how webpack emits results

        path: path.resolve(__dirname, "dist"), // string
        // the target directory for all output files
        // must be an absolute path (use the Node.js path module)

        filename: "all.js", // string
    },

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['es2015'],
                        plugins: [require('babel-plugin-transform-object-rest-spread')]
                    }
                }
            }
        ]
    }
};
  • 安裝 es6 插件:cnpm i --save-dev babel-preset-es2015;
  • 安裝 babel-loader:cnpm i --save-dev babel-loader

安裝 React 并進(jìn)行配置:

  • 安裝:cnpm i --save-dev react,cnpm i --save-dev react-dom;
  • 配置修改:presets: ['es2015','react']
    • 運行 webpack 報錯,提示缺少 preset 配置,進(jìn)行安裝:cnpm i --save-dev babel-preset-react;
  • webpack.config.js 添加設(shè)置 watch:
/**
 * Created by YJW on 2018/4/2.
 */
const path = require('path');

module.exports = {
    entry: "./app/main.js", // string | object | array

    output: {
        // options related to how webpack emits results

        path: path.resolve(__dirname, "dist"), // string
        // the target directory for all output files
        // must be an absolute path (use the Node.js path module)

        filename: "all.js", // string
    },

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['es2015','react'],
                        plugins: [require('babel-plugin-transform-object-rest-spread')]
                    }
                }
            }
        ]
    },

    watch:true
};
  • 問題:
    • 為什么用 npm 安裝 React:
      • 因為使用時候不是在 script 標(biāo)簽中引用,而是通過 import 進(jìn)行導(dǎo)入。

創(chuàng)建組件

  • 創(chuàng)建一個組件 App:
    • 自定義組件名稱一定要大寫;
    • React 要求自定義組件的類必須繼承于 React.Component 類;
    • render:渲染方法,直接調(diào)用,返回一個 JSX 語法,非常牛逼的語法。
  • App.js 中的內(nèi)容如下:
import React from "react";
class App extends React.Component{
    render(){
        return (<h1>哈哈哈123</h1>);
    }
}
export default App;

或者使用如下方式:({}:自動解構(gòu)、枚舉;如果沒有使用 {},就是 default 暴露的)

import React,{Component} from "react";
class App extends Component{
    render(){
        return <h1>嘿嘿嘿</h1>
    }
}
export default App;
  • 創(chuàng)建一個 main.js 文件,在該文件中使用組件:
    • 使用、掛載組件,有兩個參數(shù);
    • 第一個參數(shù)是 JSX 語法;
    • 第二個參數(shù)表示組件掛載到哪里。
import React from "react";
import {render} from "react-dom";
import App from "./App";

render(
    <App/>,
    document.getElementById("yjw11")
);
  • 在 demo.html 中引入 all.js。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>demo</title>

</head>
<body>
    <div id="yjw11"></div>
    <script src="dist/all.js"></script>
</body>
</html>
這樣就實現(xiàn)的組件的創(chuàng)建與展示。

JSX 語法簡單介紹

  • JSX 不能直接運行,是被 babel-loader 中的 react 這個 preset 翻譯的;

  • 注意:

    • 如果有多個 DOM,必須被一個 DOM 包裹,然后返回;
    • 標(biāo)簽必須封閉;
    • class 要寫成 className,for 要寫成 htmlFor;
    • html 注釋不能使用,只能使用 js 注釋;
    • 在 html 原有標(biāo)簽中添加自定義屬性需要加 data- 前綴,如果是自定義標(biāo)簽,屬性定義沒有特殊要求。
  • JSX 可以使用 {} 表示臨時插入一個 js 簡單表達(dá)式,不能是 for、if 等復(fù)雜結(jié)構(gòu),可以是 &&、|| 等邏輯關(guān)系運算符或者三元運算符等,可以調(diào)用函數(shù)。

  • 樣式,推薦使用內(nèi)聯(lián)樣式,使用雙花括號設(shè)置:{{}},如下所示:

class App extends Component{

    render(){
        var myStyle = {"width":100,"height":20,"color":"red"};
        return <h1 style={myStyle}>嘿嘿嘿</h1>
    }
}
  • 數(shù)組:JSX 允許在模板中插入數(shù)組,數(shù)組會自動展開所有成員。
render(){
        //定義一個數(shù)組,定義的 JSX 項目上要求有 key 屬性,只要是重復(fù)的數(shù)組項目,都要有不能重復(fù)的 key 屬性。
        var liArr = ["aaa","bbb","ccc"].map((item,index)=>{
            return <li key={index}>{item}</li>;
        });
        return (
            <ul>{liArr}</ul>
        )
    }

React 中的數(shù)據(jù)傳遞

  • React 中的數(shù)據(jù)傳遞三兄弟:state、props、context。
0.修改視圖
  • 要求改變變量的值,并修改頁面:
import React,{Component} from "react";

class App extends Component{
    constructor(){
        super();
        this.a = 100;
    }

    add(){
        this.a = this.a + 1;
        console.log("aaa");
        console.log(this.a);
    }
    render(){
        return (
            //bind不會刺激函數(shù)運行,call 和 apply 會刺激函數(shù)運行
            <h1 onClick={(this.add).bind(this)}>{this.a}</h1>
        )
    }
}
export default App;
上述案例中,a 的值發(fā)生了改變,但是頁面并沒有發(fā)生改變。即在 React 中,`組件自己屬性的變化不會引起視圖的變化`。閉包中的值變化不會引起視圖的變化
綁定監(jiān)聽使用 onClick、onMousedown、onMouseenter、onBlur,把 on 后面的字母大寫,React 會自動識別 React 事件;
綁定函數(shù)的時候,this 上下文是有問題的,所以要使用 bind() 方法來設(shè)置上下文;
綁定監(jiān)聽函數(shù)的時候,注意使用 {},而不是 ”“。
  • 只有改變?nèi)值艿闹担拍芤?Virtual DOM 的改變,從而改變 DOM。
1.state
  • 定義 state:在構(gòu)造函數(shù)中使用 this.state 屬性即可;
  • 使用 state:在 JSX 中 {this.state.a}
  • 改變 state:this.setState({a:this.state.a+1});
  • state 是內(nèi)部的(也叫 local state),只有組件自己能改變自己的 state,別人不可以更改。
import React,{Component} from "react";

class App extends Component{
    constructor(){
        super();
        this.state = {
            a:100,
            b:200,
            c:300
        }
    }

    add(){
        this.setState({a:this.state.a + 1})
    }
    render(){
        return (
            <div>
                <p>我這里有 state</p>
                <input type="button" value="點我加 1" onClick={(this.add).bind(this)}/>
                <p>state 的值:{this.state.a}</p>
            </div>
        )
    }
}
2.props
  • 利用 props 使父組件向子組件傳值,又因為此屬性是只讀的,所以只能單向綁定:
  • main.js:
import React from "react";
import {render} from "react-dom";
import App from "./App";

render(
    <App tt={111}/>,
    document.getElementById("yjw")
);
  • App.js:
import React,{Component} from "react";

class App extends Component{
    render(){
        return (
            <p>props 的值:{this.props.tt}</p>
        )
    }
}
export default App;
  • 如果需要在子組件的構(gòu)造函數(shù)中,使用 props,只需要在構(gòu)造函數(shù)中接收一個參數(shù)(第一個參數(shù)是 props,第二個參數(shù)是 context(下面會講到)):
import React,{Component} from "react";

class App extends Component{
    constructor(haha){
        super();
        alert(haha.tt)
    }

    render(){
        return (
            <p>props 的值:{this.props.tt}</p>
        )
    }
}
export default App;
  • 如果需要在子組件中對 props 的值進(jìn)行更改,可以配合 state 實現(xiàn):
import React,{Component} from "react";

class App extends Component{
    constructor(haha){
        super();

        this.state = {
            tt : haha.tt
        }
    }

    add(){
        this.setState({tt:this.state.tt+1})
    }

    render(){
        return (
            <p onClick={(this.add).bind(this)}>props 的值:{this.state.tt}</p>
        )
    }
}
export default App;
props 的屬性可以被驗證有效性:
  • 安裝:cnpm i --save-dev prop-types;
  • main.js 文件中的內(nèi)容:
import React from "react";
import {render} from "react-dom";
import App from "./App";

render(
    <App tt={111} t2="abc" t3={22}/>,
    document.getElementById("yjw")
);
  • App.js 文件中的內(nèi)容:
    • 類名.propTypes,值是一個 JSON,key 就是需要傳進(jìn)來的 props 屬性名,value 就是對它的限制。
import React,{Component} from "react";
import {PropTypes} from "prop-types";

class App extends Component{
    render(){
        return (
            <p>props 的值:{this.props.tt}</p>
        )
    }
}

App.propTypes = {
    tt:PropTypes.number.isRequired,
    t2:PropTypes.string.isRequired,
    t3:PropTypes.number
};

export default App;

更多詳細(xì)驗證,請參考這里

子組件向父組件傳值
  • 如果非要從下到上傳輸數(shù)據(jù):子組件把數(shù)據(jù)傳送給父組件,此時只能使用奇淫技巧,就是父組件傳一個函數(shù)給子組件,子組件通過傳參數(shù)將數(shù)據(jù)返回給父組件,父組件的函數(shù)接受實參改變父組件中的 state 等值。
  • 父組件中的代碼,F(xiàn)a.js:
import React,{Component} from "react";
import Son from "./Son";

class Fa extends Component{
    constructor(){
        super();
        this.state = {
            num1:111
        }
    }

    add(number){
        this.setState({"num1":number});
    }

    render(){
        return (
            <div>
                <Son num1={this.state.num1} add={(this.add).bind(this)}/>
                <h2>這里是父組件:{this.state.num1}</h2>
            </div>
        )
    }
}

export default Fa;
  • 子組件中的代碼,Son.js:
import React,{Component} from "react";

class Son extends Component{
    constructor(props){
        super();
        this.state = {
            num1:props.num1
        };

        this.add = ()=>{
            this.setState({"num1":this.state.num1+1});
            props.add(this.state.num1 + 1)
        }
    }

    render(){
        console.log(this.state);
        return <h1 onClick={(this.add).bind(this)}>我是子組件:{this.state.num1}</h1>
    }
}

export default Son;
3.context
  • 上下文的精髓是可以跨級傳遞數(shù)據(jù),爺爺組件可以直接傳遞數(shù)據(jù)給孫子。
正常傳值
  • 爺爺組件,Grand.js:
import React,{Component} from "react";
import Fa from "./Fa";

class Grade extends Component{
    constructor(){
        super();
        this.state = {
            a:100
        }
    }

    render(){
        return (
            <div>
                <h1>爺爺</h1>
                <Fa a={this.state.a}/>
            </div>
        )
    }
}

export default Grade;
  • 爸爸組件,F(xiàn)a.js:
import React,{Component} from "react";
import Son from "./Son";

class Fa extends Component{
    render(){
        return (
            <div>
                <h2>爸爸</h2>
                <Son a={this.props.a}/>
            </div>
        )
    }
}

export default Fa;
  • 孫子組件,Son.js:
import React,{Component} from "react";

class Son extends Component{

    render(){
        return (
            <div>
                <h3>孫子:{this.props.a}</h3>
            </div>
        )
    }
}

export default Son;
使用 context
  • 在要傳數(shù)據(jù)的上級實例對象中要設(shè)置”得到孩子上下文“:
getChildContext(){
    return {
        a:this.state.a
    }
}
  • 在要傳數(shù)據(jù)的上級設(shè)置孩子上下文內(nèi)容類型childContextTypes
Grade.childContextTypes = {
    a:PropTypes.number.isRequired
};
  • 在要獲取數(shù)據(jù)的下級要設(shè)置上下文內(nèi)容類型 contextTypes
Son.contextTypes = {
    a:PropTypes.number
};
  • 結(jié)論:

    • 當(dāng)上級元素中更改了上下文的數(shù)據(jù),此時所有的下級元素中的數(shù)據(jù)都會發(fā)生改變,視圖也會更新;
    • 反之不然,下級元素中數(shù)據(jù)改變,上級元素中的數(shù)據(jù)不會發(fā)生改變??梢哉J(rèn)為上下文中的數(shù)據(jù)在下級元素中是只讀的。此時如果需要在下級元素中修改上級元素中的數(shù)據(jù),就需要在 context 中共享一個操作上級元素的方法,子孫元素通過上下文獲得這個函數(shù),從而操作上級元素的值。
    • state 是自治的不涉及傳值的事兒;props 是單向的,上級 --> 下級;context 也是單向的,上級 --> 下級。如果要反向,就需要傳入一個函數(shù)。
  • 爺爺組件,Grade.js:

import React,{Component} from "react";
import Fa from "./Fa";
import PropTypes from "prop-types"

class Grade extends Component{

    constructor(){
        super();
        this.state = {
            a:100
        }
    }

    addA(){
        this.setState({a:this.state.a+1})
    }

    render(){
        return (
            <div>
                <h1 onClick={()=>{this.setState({a:this.state.a+1})}}>爺爺:{this.state.a}</h1>
                <Fa/>
            </div>
        )
    }

    //得到孩子上下文,實際上這里表示一種設(shè)置
    getChildContext(){
        return {
            a:this.state.a,
            addA:(this.addA).bind(this)
        }
    }
}

Grade.childContextTypes = {
    a:PropTypes.number.isRequired,
    addA:PropTypes.func.isRequired
};

export default Grade
  • 爸爸組件,F(xiàn)a.js:
import React,{Component} from "react";
import Son from "./Son";
import PropTypes from "prop-types";

class Fa extends Component{
    render(){
        return (
            <div>
                <h2>爸爸</h2>
                <Son/>
            </div>
        )
    }
}

export default Fa;
  • 孫子組件,Son.js:
import React,{Component} from "react";
import PropTypes from "prop-types";

class Son extends Component{
    constructor(props,context){
        super();
        console.log(context.addA);
    }

    render(){
        return (
            <div>
                <h3 onClick={this.context.addA}>孫子:{this.context.a}</h3>
            </div>
        )
    }
}

Son.contextTypes = {
    a:PropTypes.number,
    addA:PropTypes.func
};

export default Son;
  • context 使用頻率不高,傳值基本使用 props,除非很深的上下級進(jìn)行傳值才會使用。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容