19-React-02 React 之高级使用

前言

  • React 中使用了 JSX 语法糖,是一种可以将 HTML 和 JS 揉着写的语法糖;
  • 浏览器不能直接运行 JSX 语法糖,需要使用 babel 来翻译;
  • 如果使用了 babel,就可以写 ES6 代码了;
  • 写一点翻译一点非常不方便,所以要用 webpack 结合 babel-loader 来 watch 文件;
  • 既然使用了 webpack,那我们就可以进行模块化开发了。

创建项目

  • 创建一个项目文件夹,在这个文件夹中配置 webpack + babel 环境,让 webpack 可以指导 babel 翻译 ES6 语法:

    • 创建 package.json:cnpm init
  • 安装 webpack,并且设置为项目依赖:

    • cnpm i --save-dev webpack;需要在全局环境中已经安装好 webpack。
  • 创建 webpack.config.js 文件:参照官网: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
    }
}
创建完对应的项目文件之后,在控制台直接执行 webpack,就可以看到 all.js 文件。
到目前,我们已经可以进行标准的 CMD 模块化开发了。
  • 我们再引入 babel-loader 来翻译 ES6,然后修改 webpack.config.js 中的内容。
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 并进行配置:

  • 安装:cnpm i --save-dev react,cnpm i --save-dev react-dom
  • 配置修改:presets: ['es2015','react']
    • 运行 webpack 报错,提示缺少 preset 配置,进行安装:cnpm i --save-dev babel-preset-react
  • webpack.config.js 添加设置 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 标签中引用,而是通过 import 进行导入。

创建组件

  • 创建一个组件 App:
    • 自定义组件名称一定要大写;
    • React 要求自定义组件的类必须继承于 React.Component 类;
    • render:渲染方法,直接调用,返回一个 JSX 语法,非常牛逼的语法。
  • App.js 中的内容如下:
import React from "react";
class App extends React.Component{
    render(){
        return (<h1>哈哈哈123</h1>);
    }
}
export default App;

或者使用如下方式:({}:自动解构、枚举;如果没有使用 {},就是 default 暴露的)

import React,{Component} from "react";
class App extends Component{
    render(){
        return <h1>嘿嘿嘿</h1>
    }
}
export default App;
  • 创建一个 main.js 文件,在该文件中使用组件:
    • 使用、挂载组件,有两个参数;
    • 第一个参数是 JSX 语法;
    • 第二个参数表示组件挂载到哪里。
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>
这样就实现的组件的创建与展示。

JSX 语法简单介绍

  • JSX 不能直接运行,是被 babel-loader 中的 react 这个 preset 翻译的;

  • 注意:

    • 如果有多个 DOM,必须被一个 DOM 包裹,然后返回;
    • 标签必须封闭;
    • class 要写成 className,for 要写成 htmlFor;
    • html 注释不能使用,只能使用 js 注释;
    • 在 html 原有标签中添加自定义属性需要加 data- 前缀,如果是自定义标签,属性定义没有特殊要求。
  • JSX 可以使用 {} 表示临时插入一个 js 简单表达式,不能是 for、if 等复杂结构,可以是 &&、|| 等逻辑关系运算符或者三元运算符等,可以调用函数。

  • 样式,推荐使用内联样式,使用双花括号设置:{{}},如下所示:

class App extends Component{

    render(){
        var myStyle = {"width":100,"height":20,"color":"red"};
        return <h1 style={myStyle}>嘿嘿嘿</h1>
    }
}
  • 数组:JSX 允许在模板中插入数组,数组会自动展开所有成员。
render(){
        //定义一个数组,定义的 JSX 项目上要求有 key 属性,只要是重复的数组项目,都要有不能重复的 key 属性。
        var liArr = ["aaa","bbb","ccc"].map((item,index)=>{
            return <li key={index}>{item}</li>;
        });
        return (
            <ul>{liArr}</ul>
        )
    }

React 中的数据传递

  • React 中的数据传递三兄弟: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不会刺激函数运行,call 和 apply 会刺激函数运行
            <h1 onClick={(this.add).bind(this)}>{this.a}</h1>
        )
    }
}
export default App;
上述案例中,a 的值发生了改变,但是页面并没有发生改变。即在 React 中,`组件自己属性的变化不会引起视图的变化`。闭包中的值变化不会引起视图的变化
绑定监听使用 onClick、onMousedown、onMouseenter、onBlur,把 on 后面的字母大写,React 会自动识别 React 事件;
绑定函数的时候,this 上下文是有问题的,所以要使用 bind() 方法来设置上下文;
绑定监听函数的时候,注意使用 {},而不是 ”“。
  • 只有改变三兄弟的值,才能引起 Virtual DOM 的改变,从而改变 DOM。
1.state
  • 定义 state:在构造函数中使用 this.state 属性即可;
  • 使用 state:在 JSX 中 {this.state.a}
  • 改变 state:this.setState({a:this.state.a+1})
  • state 是内部的(也叫 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;
  • 如果需要在子组件的构造函数中,使用 props,只需要在构造函数中接收一个参数(第一个参数是 props,第二个参数是 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 的值进行更改,可以配合 state 实现:
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 文件中的内容:
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 文件中的内容:
    • 类名.propTypes,值是一个 JSON,key 就是需要传进来的 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;

更多详细验证,请参考这里

子组件向父组件传值
  • 如果非要从下到上传输数据:子组件把数据传送给父组件,此时只能使用奇淫技巧,就是父组件传一个函数给子组件,子组件通过传参数将数据返回给父组件,父组件的函数接受实参改变父组件中的 state 等值。
  • 父组件中的代码,Fa.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
  • 上下文的精髓是可以跨级传递数据,爷爷组件可以直接传递数据给孙子。
正常传值
  • 爷爷组件,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;
  • 爸爸组件,Fa.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
  • 在要传数据的上级实例对象中要设置”得到孩子上下文“:
getChildContext(){
    return {
        a:this.state.a
    }
}
  • 在要传数据的上级设置孩子上下文内容类型childContextTypes
Grade.childContextTypes = {
    a:PropTypes.number.isRequired
};
  • 在要获取数据的下级要设置上下文内容类型 contextTypes
Son.contextTypes = {
    a:PropTypes.number
};
  • 结论:

    • 当上级元素中更改了上下文的数据,此时所有的下级元素中的数据都会发生改变,视图也会更新;
    • 反之不然,下级元素中数据改变,上级元素中的数据不会发生改变。可以认为上下文中的数据在下级元素中是只读的。此时如果需要在下级元素中修改上级元素中的数据,就需要在 context 中共享一个操作上级元素的方法,子孙元素通过上下文获得这个函数,从而操作上级元素的值。
    • state 是自治的不涉及传值的事儿;props 是单向的,上级 --> 下级;context 也是单向的,上级 --> 下级。如果要反向,就需要传入一个函数。
  • 爷爷组件,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>
        )
    }

    //得到孩子上下文,实际上这里表示一种设置
    getChildContext(){
        return {
            a:this.state.a,
            addA:(this.addA).bind(this)
        }
    }
}

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

export default Grade
  • 爸爸组件,Fa.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,除非很深的上下级进行传值才会使用。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容