美文网首页
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