美文网首页
19-React-02 React 之高级使用

19-React-02 React 之高级使用

作者: magic_pill | 来源:发表于2018-04-03 12:39 被阅读22次

前言

  • 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.a).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,除非很深的上下级进行传值才会使用。

相关文章

网友评论

      本文标题:19-React-02 React 之高级使用

      本文链接:https://www.haomeiwen.com/subject/jnvzcftx.html