• 关于React的第一篇笔记


    目录

    1.入门

    1.1开发环境

    1.1.1在线开发环境

    https://codesandbox.io/s/cool-tdd-xwzml,通过codesand在线编辑一个新的react项目,并且可以在线调试

    image-20200204160431565

    1.1.2本地开发环境

    如果觉得网上的效率有点慢,可以参考下面的方式来安装react

    在node.js窗口中,输入下面命令即可全局安装react脚手架

    npm install create-react-app -g
    

    然后利用脚手架创建我们自己的项目,输入下面的命令:

    create-react-app zdj-project
    

    执行完上述命令后,就可以安装了,会安装一系列依赖,比如react-dom,react-script等等。安装需要几分钟,需要耐心等待一下,出现下图所示的文字,说明已经创建项目成功了。

    image-20200226151941314

    Created git commit.
    
    Success! Created zdj-project at D:zdjzdj-project
    Inside that directory, you can run several commands:
    
      npm start
        Starts the development server.
    
      npm run build
        Bundles the app into static files for production.
    
      npm test
        Starts the test runner.
    
      npm run eject
        Removes this tool and copies build dependencies, configuration files
        and scripts into the app directory. If you do this, you can’t go back!
    
    We suggest that you begin by typing:
    
      cd zdj-project
      npm start
    
    Happy hacking!
    
    cd zdj-project
    

    输入上述命令进入我们的项目,然后查看package.json文件,发现已经内置了一些命令,所以会有上图所示的命令来执行结果,如下图所示。

    image-20200226152244803

    start命令表示本地启动开发服务器,执行npm start命令,就可以打开react官方的页面了,如下图所示。

    image-20200226152415111

    查看项目中的index.js文件,index.js是react项目的主入口文件,利用ReactDOM库处理react和dom元素,ReactDOM.render方法来进行渲染组件,render的第一个参数就是要渲染的模块组件,第2个是要渲染的元素。可以参照着APP.js的范例写我们自己的组件。修改渲染的内容,我们看到页面也会跟着变化的。

    image-20200226155916744

    1.2 知识点介绍

    1.2.1 从一个简单的Hello world开始

    import React from "react";
    
    import ReactDOM from "react-dom";
    
    
    const rootElement = document.getElementById("root");
    
    ReactDOM.render(<h1>Hello, world3!</h1>, rootElement);
    

    以上代码就是一个最简单的hello world,接下来看下面的代码

    import React from "react";
    import ReactDOM from "react-dom";
    
    const content = <h1>Hello world</h1>;
    const rootElement = document.getElementById("root");
    ReactDOM.render(content, rootElement);
    
    

    跟第一个代码块中的区别是const content = Hello world;它被称为 JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以生成 React “元素”。在jsx中可以嵌入表达式,如下代码,就会输出

    Hello, zhangdongjuan的样式,在 JSX 语法中,你可以在大括号内放置任何有效的 [JavaScript 表达式]。

    在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。你应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号。

    import React from "react";
    import ReactDOM from "react-dom";
    
    const rootElement = document.getElementById("root");
    const name = "zhangdongjuan";
    const content = <h1>Hello, {name}</h1>;
    ReactDOM.render(content, rootElement);
    
    output:Hello, zhangdongjuan
    
    import React from "react";
    import ReactDOM from "react-dom";
    
    const rootElement = document.getElementById("root");
    
    const user = {
      firstName: "zhang",
      lastName: "dongjuan"
    };
    function formatName(user) {
      return user.firstName + " " + user.lastName;
    }
    
    const content = <h1>Hello, {formatName(user)}</h1>;
    ReactDOM.render(content, rootElement);
    
    output:Hello, zhang dongjuan
    

    可以将上述内容放到一个组件中,通过import方式将其引用进来,如下图所示,创建一个Welcome.js文件

    文件如下:

    import React from 'react';
    
    function Welcome() {
        return (
          <h1>
              hello,world!!
          </h1>
        );
    }
    
    export default Welcome;
    

    通过下面的方式将Welcome引用到index.js文件,可以看到界面已经刷新了

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import * as serviceWorker from './serviceWorker';
    import Weclome from './Welcome';
    
    
    ReactDOM.render(<Weclome />, document.getElementById('root'));
    
    // If you want your app to work offline and load faster, you can change
    // unregister() to register() below. Note this comes with some pitfalls.
    // Learn more about service workers: https://bit.ly/CRA-PWA
    serviceWorker.unregister();
    
    

    可以看到React里面的模块化思想还是很明显的啦。

    1.2.2 jsx知识点分析

    查看上面的Welcome.js里面的内容,可以看出又有html语言,又有javascript语言,这是JSX,

    1. jsx(是xml+js),浏览器不能直接识别,要通过babel转换为纯的js语法才能识别。
    2. 用jsx来创建虚拟DOM,最终会产生一个JS对象;
    3. jsx不是字符串,也不是HTML/XML标签
    4. jsx的标签+js代码,看到"<"就是标签,看到大括号就是js代码,
    5. 只要声明了jsx语法,都要加上type="babel"才能编译

    比较简单,就是花括号的概念,在花括号里面可以添加任何的javascript表达式,表达式就是一段有效的代码返回的值。比如在代码里面添加{'viking'},{1},{2+3},运行发现如果是字符串,直接打印,如果是数字的话,会进行运算。

    如下图所示,添加{}内容,代码如下:

    function Welcome() {
        return (
            <div>
                <h1>
                    hello,world!!
                </h1>
                {1+3}
                {'dddd'}
            </div>
        );
    }
    
    export default Welcome;
    
    output---hello,world!!  4dddd
    

    1.2.2.1可以看看jsx里面能不能放数组,会将数组的每一项都输出。

    import React from 'react';
    
    function Welcome() {
        return (
            <div>
                <h1>
                    hello,world!!
                </h1>
                {[1,2,3]}
            </div>
        );
    }
    
    export default Welcome;
    
    output---- 123
    

    1.2.2.2 如果在{}里面嵌入jsx表达式,表达式也会被解析。

    但是jsx表达式其实是不需要放到{}里面的,{}删除掉,也是可以正常解析的。

    import React from 'react';
    
    function Welcome() {
        return (
            <div>
                <h1>
                    hello,world!!
                </h1>
                {<p>this is new fafa</p>}
            </div>
        );
    }
    
    export default Welcome;
    
    output ---this is fafa
    

    1.2.2.3 下面用jsx实现一个列表,类似angular里面的ng-repeat

    import React from 'react';
    
    function Welcome() {
        const todoList=['Learn react','Learn redux'];
        return (
            <div>
                <h1>
                    hello,world!!
                </h1>
                {
                    todoList.map(item=>
                        <li>{item}</li>
                    )
                }
            </div>
        );
    }
    
    export default Welcome;
    

    todoList.map()方法是返回一个新数组,运行npm start命令之后,就可以得到列表了,如下图所示:

    image-20200228151949408

    1.2.2.4 jsx实现ng-if

    如下代码所示,用jsx来实现ng-if的功能,使用三元表达式可以实现

    import React from 'react';
    
    function Welcome() {
        const isLogin = true;
        return (
            <div>
                <h1>
                    hello,world!!
                </h1>
                {isLogin?<p>你已经登录</p>:<p>请登录</p>}
            </div>
        );
    }
    
    export default Welcome;
    
    ----output 你已经登录
    

    1.2.2.5 jsx的特殊属性

    jsx的属性和html属性是一致的,有2个特例,标签上的class属性和javascipt的保留字是一样的,这里要起个别名,叫className;for也是一样的,可以改成htmlFor

    1.2.2.6 babel编译

    jsx利用babel编译解析,是React.createElement() 的语法糖。babel将jsx编译成js代码,才能被浏览器识别。

    1.2.2.7 虚拟DOM

    一直说React创建了一个虚拟DOM,虚拟DOM比较轻,属性比较少;真实DOM是比较重的对象,属性比较多;

    更新真实DOM,页面会发生变化,页面会重新绘制;更新虚拟DOM,页面不会发生变化,要等渲染的时候页面才会发生变化。

    可以用debugger来,看看虚拟DOM和真实DOM的属性。

    1.2.2.8 模块与组件和模块化与组件化的理解

    模块:向外提供特定功能的js文件;数据+对数据的操作+=》将私有的函数向外暴露

    复用js,简化js的编写,提高js运行效率;

    组件:用来实现特定功能效果的代码集合(html/css/js),组成界面功能的所有集合

    一个界面的局部功能模块,比如一个界面分为上,中,下3个组件

    模块化:当应用的js都以模块来编写的,

    组件化:根据组件的方式写项目代码,应用是以多组件的方式来实现

    1.2.2.9 react面向组件编程的简单实战

    js面向对象编程--》面向模块变成--》面向组件编程

    组件标签--》首字母大写,对应的是html标签

    组件化编码的过程:

    定义组件

    渲染组件标签

    定义组件的方式:

    1.工厂函数组件,必须返回虚拟DOM,也就是返回标签对象,(MycomponetFunction.js)代码如下

    import React, { Component } from 'react';
    
    function MycomponetFunction() {
        return (<h1> Mycomponet function  </h1>);
    }
    

    渲染的时候才会调用这个组件,在页面的React调试工具里面可以看到组件的标签

    2.ES6类组件,调用render方法获得虚拟DOM,创建完实例就会调用render方法,如下代码

    可以看出利用class实现的render方法里面的内容,和函数组件组件里面返回的内容是一样的。

    函数组件更简单一点,看起来。(MycomponetClass.js)代码如下

    import React, { Component } from 'react';
    
    class MycomponetClass extends React.Component {
        render() {
            return (<h1> Mycomponet class </h1>);
        }
    }
    export default MycomponetClass;
    

    利用组件化的编程思想,将2种创建组件的方式显示在同一个组件里面,然后渲染到页面上,如下的代码(CombineComponent.js),同时我们可以用{{}}的方式给div添加样式内容。 return返回的内容如果有多个,但是只有一个div

    import React from 'react';
    import MycomponetClass from './MycomponentClass'
    import MycomonentFunction from './MycomponentFunction'
    
    
    class CombineComponent extends React.Component {
        render() {
            return (
                <div style={{'background-color': 'aliceblue','width':'500px'}}>   
                    <MycomonentFunction> </MycomonentFunction>
                    <MycomponetClass> </MycomponetClass>              
                </div>
            )        
        }
    }
    export default CombineComponent;
    
    1.2.2.9.1 react实战--state属性

    实现一个简单的例子,实现计数器的功能,如下代码是用class的方式来实现的

    class MycomponetClass extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                count: 0
            }
        }
    
        click() {
            console.log(this + "");    //undefined
        }
    
        render() {
            console.log(this);   //组件本身
            return (
                <div>
                    <h1> Mycomponet class </h1>
                    <h1> You have click {this.state.count} times</h1>
                    <h1> <button onClick={this.click}>click me</button> </h1>
                </div>
            );
        }
    }
    export default MycomponetClass;
    

    class里面通过state改变变量的属性,输出2个this,可以发现,

    第一个this,是按钮的点击事件,输出undefined;

    第2个this,输出组件本身,以及组件里面的方法,如下所示,是输出的this的内容

    MycomponetClass {props: {…}, context: {…}, refs: {…}, updater: {…}, state: {…}, …}
    isMounted: (...)
    replaceState: (...)
    props: {children: " "}
    context: {}
    refs: {}
    updater: {isMounted: ƒ, enqueueSetState: ƒ, enqueueReplaceState: ƒ, enqueueForceUpdate: ƒ}
    state: {count: 0}
    _reactInternalFiber: FiberNode {tag: 1, key: null, stateNode: MycomponetClass, elementType: ƒ, type: ƒ, …}
    _reactInternalInstance: {_processChildContext: ƒ}
    __proto__: Component
    isMounted: (...)
    replaceState: (...)
    constructor: class MycomponetClass
    click: ƒ click()
    render: ƒ render()
    __proto__: Object
    

    click方法里面是访问不到this本身的,计数器的数字就增加不了,要绑定this,如下代码,这样点击按钮之后,计数器就可以做增加了。

    import React, { Component } from 'react';
    
    class MycomponetClass extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                count: 0
            }
            this.click = this.click.bind(this);  //click方法绑定this
        }
    
        click() {
            console.log(this + "");
            this.setState({count:this.state.count+1})
        }
    
        render() {
            console.log(this);
            return (
                <div>
                    <h1> Mycomponet class </h1>
                    <h1> You have click {this.state.count} times</h1>
                    <h1> <button onClick={this.click}>click me</button> </h1>
                </div>
            );
        }
    }
    export default MycomponetClass;
    

    但是我们发现当每次点击之后,控制台输出的内容如下,说明每次点击之后,render函数都被执行了一次,是因为我们调用了setState方法,每次页面会重新渲染,render方法都会再次被调用。

    [object Object]
    MycomponentClass.jsx:18 MycomponetClass {props: {…}, context: {…}, refs: {…}, updater: {…}, state: {…}, …}
    MycomponentClass.jsx:13 [object Object]
    MycomponentClass.jsx:18 MycomponetClass {props: {…}, context: {…}, refs: {…}, updater: {…}, state: {…}, …}
    

    但是当我们删除setState方法之后,控制台里面只会打印出[object Object]的内容,说明class并没有被渲染,这样的我们更清楚地明白了setState方法 的作用。

    这个时候你心里会不会有个问题:为什么要用state声明和setState方法呢??可以试一下如果不用state方法,仅仅改变类里面的变量值,效果是什么样子的,如下 代码

    import React, { Component } from 'react';
    
    class MycomponetClass extends React.Component {
       constructor(props) {
            super(props)
            this.state = {
                count: 0
            }
            this.a = 0;
            this.click = this.click.bind(this);
        }
    
        click() {
            console.log(this.a);
            this.a = this.a + 1;
        }
    
        render() {
            console.log(this);
            return (
                <div>
                    <h1> Mycomponet class </h1>
                    <h1> You have click {this.a} times</h1>
                    <h1> <button onClick={this.click}>click me</button> </h1>
                </div>
            );
        }
    }
    export default MycomponetClass;
    

    上述代码运行之后,发现点击按钮之后虽然this.a发生了变化,但是界面上显示的次数

    就不会发生变化了,因为页面不会重新刷新,render函数不会被重新调用,这样子state的作用就显而易见了。

    关于state这里有个ES6的小技巧,如下代码,利用ES6对象的解析赋值来获取state里面对象的值。

    render() {
            console.log(this);
            const { count } = this.state;   //ES6解构语法
       
            return (
                <div>
                    <h1> Mycomponet class </h1>
                    <h1> You have click {count} times</h1>
                    <h1> <button onClick={this.click}>click me</button> </h1>
                </div>
            );
        }
    

    这里有没有一个疑问:能不能用工厂函数式组件呢???

    我们实现的class里面有state状态,就只能用类组件了。如果是没有状态的组件,就可以用函数组件了。

    1.2.2.9.2 react实战--prop属性

    类似于img,本身并没有state的信息,而且通过标签属性的方式传递进去,来使其显示。

    如果一个组件的内部没有状态,使用工厂函数的方式,比如要显示2个人的信息(性别,姓名,年龄),这些信息由外部传入信息给组件,

    import React, { Component } from 'react';
    
    function MycomponetFunction(props) {
        return (
            <div>
                <h1> Mycomponet function </h1>
                <ul>
                    <li>姓名:{props.name}</li>
                    <li>年龄:{props.age}</li>
                    <li>性别:{props.sex}</li>
                </ul>
            </div>
        );
    }
    export default MycomponetFunction;
    

    直接运行发现会报错,因为我们没有给函数组件传递内容,调用函数的时候,我们要传递这些参数

    import React from 'react';
    import MycomponetClass from './MycomponentClass'
    import MycomonentFunction from './MycomponentFunction'
    
    
    class CombineComponent extends React.Component {
        render() {
            const p1 = {
                name: "zdj",
                age: 19,
                sex: '女'
            }
            return (
                <div style={{ 'background-color': 'aliceblue', 'width': '500px' }}>
                    <MycomonentFunction name={p1.name} age={p1.age} sex={p1.age}> </MycomonentFunction>
                    <MycomponetClass> </MycomponetClass>
                </div>
            )
        }
    }
    export default CombineComponent;
    

    这样就不会报错了,我们的内容也会正常显示了,如下:

    • 姓名:zdj
    • 年龄:19
    • 性别:19

    调用组件MycomonentFunction多次,就可以显示多个人的信息了。

    如果我们想要设置默认属性呢?

    //指定属性默认值:
    MycomponetFunction.defaultProps={
        sex: '男',
        age: '18'
    }
    
    <MycomonentFunction {...p1}> </MycomonentFunction>
    

    通过...也可以给组件传递属性,和我们上面的方式是一样的,是ES6的语法。

    如果用class的方式来实现呢???render方法可以修改成下面的代码

      return (
            <div>
                <h1> Mycomponet function </h1>
                <ul>
                    <li>姓名:{this.props.name}</li>
                    <li>年龄:{this.props.age}</li>
                    <li>性别:{this.props.sex}</li>
                </ul>
            </div>
        );
    
    1.2.2.9.3 react实战--ref属性

    从获取一个文本框的值来理解这个属性,如果不知道用什么的时候,先用类来实现,如下图所示,用一个单独的组件里面渲染文本框,实现获取文本框内容的功能。

    import React from 'react'
    
    class MyComponentInput extends React.Component {
    
        render() {
            return (
                <div>
                    <input type="text"></input>&nbsp;&nbsp;
                    <button>提示输入</button>&nbsp;&nbsp;
                    <input type="text" placeholder="失去焦点提示数据"></input>
                </div>
            )
        }
    }
    export default MyComponentInput;
    

    如果按钮点击之后想获取文本框的内容,怎么获取文本框元素呢?以前真实DOM是通过document.getElementByTag()或者document.getElementById方法之类的,但是React是虚拟DOM,我们怎么获取文本框元素呢?那这时候引入ref,来获取文本框元素,ref相当于我们以前的id。

    import React from 'react'
    
    class MyComponentInput extends React.Component {
        constructor(props) {
            super(props);
            this.showInput = this.showInput.bind(this);
            this.handleBlur = this.handleBlur.bind(this);
        }
    
        showInput() {
            const input = this.refs.content;
            alert(input.value);
            alert(this.input.value);
        }
    
        handleBlur(event) {
            alert(event.target.value);
        }
    
        render() {
            return (
                <div>
                    <input type="text" ref="content"></input>&nbsp;&nbsp;
                    <input type="text" ref={input => this.input = input}></input>&nbsp;&nbsp;
                    <button onClick={this.showInput}> 提示输入</button>&nbsp;&nbsp;
                    <input type="text" placeholder="失去焦点提示数据" onBlur={this.handleBlur}></input>
                </div>
            )
        }
    }
    export default MyComponentInput;
    

    第一种方式:第一个input(通过ref="content")获得input文本框本身,我们在需要使用到这个文本框的地方直接用(this.refs.content)就可以获得到这个文本框了,如代码中按钮的点击事件就可以获得文本框的值了。

    第二种方式:第二个input(通过ref={input => this.input = input})箭头函数通过this.input的方式来获取文本框对象本身,这里注意的一点是通过input的输入参数传给箭头函数,这个input也是可以随意修改名称的,this.input则是可以修改名称的,这样在这个类里面就可以通过this.input来使用文本框对象了。

    最后一个文本框实现了文本框失去焦点提示数据的功能,通过this.handleBlur 方法来实现这个功能,这个方法里面传进了event 对象,通过event.target来获取文本框对象本身,如上面的代码。

    1.2.2.9.4 react实战--总结

    state是组件内部的属性,props是组件外部传入的属性,写代码的时候要把这2点区分清楚。

    功能界面的组件化编码流程:
    
    1.拆分组件:拆分页面,抽取组件,一般有一个外部组件(称之为app)(组合内部的组件)
    
    2.实现静态组件:使用组件实现静态页面效果
    
    3.实现动态组件:
    
    3.1  动态显示初始化数据;
    
    3.2  交互功能(从绑定事件监听开始)
    
    数据保存在哪个组件内?看数据是某个组件需要(给她),还是某些组件需要(给共同的父亲)
    
    需要在子组件里面改变父组件的状态,可以通过传递函数的方式
    
    状态在哪个组件,更新状态的行为就应该定义在哪个组件。
    
    父组件定义函数,传递给子组件,子组件来调用这个函数
    

    接下来通过一个小例子来分析一下编码过程

    1.首先总的组件是APP,显示标题,显示添加按钮组件和内容组件

    2.有添加按钮的组件(AddTodo),负责往3中的List添加数据内容

    3.显示内容的组件(List组件),负责显示传递的数据

    首先编写好静态文件,界面如下

    My first app -todo list
     文本框  todo按钮
    as
    sg
    gs
    
    

    分析一下数据流,因为AddTodo 组件和 List组件 都需要这个显示的数据,一个负责添加数据,一个负责显示数据,所以这个数据要放到父组件APP里面,代码如下(FirstApp.jsx)

    import React from 'react';
    import AddTodo from './AddTodo';
    import List from './List'
    
    class FirstApp extends React.Component {
    
        render() {
            const todos = [1, 2, 3];
            return (
                <div>
                    <h1>My first app -todo list</h1>
                    <AddTodo></AddTodo>
                    <List todos={todos}></List>
                </div>
            )
        }
    }
    export default FirstApp;
    

    AddTodo 组件代码(AddTodo.jsx)

    import React from 'react';
    
    class AddTodo extends React.Component {
        constructor(props) {
            super(props);
            this.getContent = this.getContent.bind(this);
        }
        getContent() {
            console.log(this.input.value);
        }
    
        render() {
            return (
                <div>
                    <input type="text" ref={input => this.input = input}></input>&nbsp;&nbsp;
                    <button onClick={this.getContent}>todo</button>
                </div>
            )
    
        }
    }
    
    export default AddTodo;
    

    List 组件(List.jsx)

    import React from 'react';
    
    class List extends React.Component {
        render() {
            return (
                <ul>
                    {
                        this.props.todos.map(
                            (todo, index) => <li key={index}>{todo}</li>
                        )}
                </ul>
            )
        }
    }
    export default List;
    

    继续完善代码,数据存在父组件App里面,但是数据的变化是在AddTodo 组件里面,所以这里的一个关键问题是父组件App里面的数据如何感知子组件的操作导致数据变化呢?子组件又是能够操作父组件里面的数据呢??

    答案就是父组件给子组件传递一个函数,子组件通过调用这个函数来改变父组件里面的数据内容

    首先在父组件里面添加修改状态的函数(FirstApp.jsx),如下代码,

    import React from 'react';
    import AddTodo from './AddTodo';
    import List from './List'
    
    class FirstApp extends React.Component {
    
        constructor(props) {
            super(props);
            this.state = {
                todos: [1, 2, 3]
            }
            this.addTodo=this.addTodo.bind(this);
        }
        
        addTodo(todo) {
            const { todos } = this.state;
            todos.unshift(todo);
            this.setState({ todos });
        }
    
        render() {
            return (
                <div>
                    <h1>My first app -todo list</h1>
                    <AddTodo addTodo={this.addTodo}></AddTodo>
                    <List todos={this.state.todos}></List>
                </div>
            )
        }
    }
    export default FirstApp;
    

    要注意一定要在FirstApp 给传递的函数添加this的指针指向,不然会报下面的错误:只要是在自定义的方法里面要更新状态,一定要记得绑定this

    TypeError: Cannot destructure property 'todos' of 'this.state' as it is undefined.
    
    this.addTodo=this.addTodo.bind(this)
    

    在子组件里面就可以调用父组件里面的方法了(this.props.addTodo(todo))

    import React from 'react';
    
    class AddTodo extends React.Component {
        constructor(props) {
            super(props);
            this.getContent = this.getContent.bind(this);
        }
        getContent() {
            const todo=this.input.value.trim();
            if(!todo) {
                return;
            }
            this.props.addTodo(todo);
            this.input.value = "";
        }
    
        render() {
            return (
                <div>
                    <input type="text" ref={input => this.input = input}></input>&nbsp;&nbsp;
                    <button onClick={this.getContent}>todo</button>
                </div>
            )
    
        }
    }
    
    export default AddTodo;
    

    这样功能就可以实现了,每点击一次按钮,数据就会被刷新:

    My first app -todo list
    文本框  todo按钮
    f
    1
    2
    3
    

    简化方式:

       constructor(props) {
            console.log("constructor");
            super(props);
            this.state = {
                opacity: 1
            }
            this.click = this.click.bind(this);
        }
        //可以写成下面的方式
        state={
            opacity: 1
        }
    
    List.propTypes={
    
    addComment:PropTypes.func.isRequired
    
    }
    
    //可以写成下面的方式
    static propTypes ={
    
    addComment:PropTypes.func.isRequired
    
    }
    

    注意使用这样的方式来获取props传递来的对象:{a,b,c}=this.props

    1.2.3 React属性

    1.2.3.1 Props属性

    组件就像一个函数一样,接受特定的输入(props),产生特定的输出(React elements),v=f(props)。

    image-20200228155024456

    安装bootstrap,让样式好看一些,如下代码所示。

    npm install bootstrap --save
    

    修改index.js内容,引入bootstrap样式文件

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import * as serviceWorker from './serviceWorker';
    import Weclome from './Welcome';
    import 'bootstrap/dist/css/bootstrap.min.css'   --this
    
    const rootElement=document.getElementById("root");
    const name="zdj"
    
    ReactDOM.render(<Weclome />, rootElement);
    
    // If you want your app to work offline and load faster, you can change
    // unregister() to register() below. Note this comes with some pitfalls.
    // Learn more about service workers: https://bit.ly/CRA-PWA
    serviceWorker.unregister();
    

    然后再src目录下新建文件夹components,在文件夹下新建NameCard.js文件,该文件代码如下所示:虽然没有完全完成,但是我们可以先保存查看其效果。

    import React, {Component} from 'react'
    
    class NameCard extends React.Component {
        render() {
            const { name,number }=this.props;
    
            return (
                <div>
                    <h4>{name}</h4>
                    <ul>
                        <li>电话:{number}</li>
                    </ul>
                </div>
            );
        }
    }
    
    export default NameCard
    

    在App.js入口文件里面引入components里面的NameCard组件,代码如下所示:

    import React from 'react';
    import './App.css';
    import  NameCard  from './components/NameCard';
    
    function App() {
      return (
        <div className="App">
          <NameCard name='viking' number='343443' />
        </div>
      );
    }
    
    export default App;
    

    这样我们看到的效果是

    viking

    • 电话:343443

    接下来继续完善App.js,如下代码所示,增加了isHuman属性,默认就是true

    import React from 'react';
    import './App.css';
    import  NameCard  from './components/NameCard';
    
    function App() {
      return (
        <div className="App">
          <NameCard name='viking' number={343442223}  isHuman />
        </div>
      );
    }
    
    export default App;
    

    修改NameCard.js内容,如下代码所示:

    import React, {Component} from 'react'
    
    class NameCard extends React.Component {
        render() {
            const { name,number,isHuman,tags }=this.props;
    
            return (
                <div>
                    <h4>{name}</h4>
                    <ul>
                        <li>电话:{number}</li>
                        <li>电话:{isHuman?'人类':'外星生物'}</li>
                    </ul>
                </div>
            );
        }
    }
    
    export default NameCard
    

    运行后的结果如下图所示:

    viking

    • 电话:343442223
    • 电话:人类

    接下来添加标签内容,先修改NameCard.js内容,标签内容是个数组,代码如下:对其进行遍历。

    import React, {Component} from 'react'
    
    class NameCard extends React.Component {
        render() {
            const {name, number, isHuman, tags} = this.props;
            return (
                <div>
                    <h4>{name}</h4>
                    <ul>
                        <li>电话:{number}</li>
                        <li>电话:{isHuman ? '人类' : '外星生物'}</li>
                        <hr/>
                        <p>
                            {tags.map((tag, index) => (
                                <span key={index}>{tag}</span>
                            ))}
                        </p>
                    </ul>
                </div>
            );
        }
    }
    
    export default NameCard
    

    然后修改App.js传入tags数组内容

    import React from 'react';
    import './App.css';
    import NameCard from './components/NameCard';
    
    function App() {
        const tags = ['恐龙', '足球小子']
        return (
            <div className="App">
                <NameCard name='viking' number={343442223} isHuman tags={tags}/>
            </div>
        );
    }
    
    export default App;
    
    

    运行npm start命令后,效果如下所示:

    • viking

      • 电话:343442223

      • 电话:人类


      • 恐龙足球小子

    由于我们引入了bootstrap组件,所以可以给各个元素添加样式,如下代码所示,给div和span添加bootstrap里面的样式:

    import React, {Component} from 'react'
    
    class NameCard extends React.Component {
        render() {
            const {name, number, isHuman, tags} = this.props;
            return (
                <div className="alert alert-success">
                    <h4>{name}</h4>
                    <ul>
                        <li>电话:{number}</li>
                        <li>电话:{isHuman ? '人类' : '外星生物'}</li>
                        <hr/>
                        <p>
                            {tags.map((tag, index) => (
                                <span className="badge badge-pill badge-primary" key={index}>{tag}</span>
                            ))}
                        </p>
                    </ul>
                </div>
            );
        }
    }
    
    export default NameCard
    

    效果如下图所示,这个就好看多了。

    image-20200229093512295

    1.2.3.2 组件的函数式写法

    修改NameCard.js内容,将组件变成一个函数,函数的入参是props,修改完了之后发现同样也可以生效,这样的方式看起来比类的方式要简单很多,但是否所有的组件都能修改成用函数来表达吗?这个后面再讨论。接下来还要注意的点是:属性是只读的,像下面的name, number, isHuman, tags 这些属性都是只读的,当修改传入的这些属性时,就会报错,这些函数都是纯函数,不修改输入参数值的函数。

    import React, {Component} from 'react'
    
    const NameCard = (props) => {
        const {name, number, isHuman, tags} = props;
        return (
            <div className="alert alert-success">
                <h4>{name}</h4>
                <ul>
                    <li>电话:{number}</li>
                    <li>电话:{isHuman ? '人类' : '外星生物'}</li>
                    <hr/>
                    <p>
                        {tags.map((tag, index) => (
                            <span className="badge badge-pill badge-primary" key={index}>{tag}</span>
                        ))}
                    </p>
                </ul>
            </div>
        );
    }
    //
    // class NameCard extends React.Component {
    //     render() {
    //         const {name, number, isHuman, tags} = this.props;
    //         return (
    //             <div className="alert alert-success">
    //                 <h4>{name}</h4>
    //                 <ul>
    //                     <li>电话:{number}</li>
    //                     <li>电话:{isHuman ? '人类' : '外星生物'}</li>
    //                     <hr/>
    //                     <p>
    //                         {tags.map((tag, index) => (
    //                             <span className="badge badge-pill badge-primary" key={index}>{tag}</span>
    //                         ))}
    //                     </p>
    //                 </ul>
    //             </div>
    //         );
    //     }
    // }
    
    export default NameCard
    

    1.2.3.3 React状态state

    从上面的代码可以看出,只有ReactDOM.render()方式可以修改组件的状态,props传进来的属性时只读的,那我们如何动态修改组件的状态呢?比较如下:

    props 是从外部传入的属性,是不能修改的

    State 是内部的属性,是私有的,组件内部的数据可以动态改变,利用this.setState()是更新state的唯一途径

    下面,通过完成一个点赞按钮来体会status的概念。首先在components文件夹下新建一个LikesButton.js文件

    文件内容如下所示,声明一个state.likes变量,初始设为0

    import React from 'react'
    
    class LikesButtons extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                likes: 0
            }
        }
    
        render() {
            return (
                <div className="likes-button-componet">
                    <button type="button" className="btn btn-outline-primary btn-lg">
                        点赞 {this.state.likes}
                    </button>
                </div>
            )
        }
    }
    
    export default LikesButtons;
    

    在App.js文件里面引入LikesButtons 组件:

    import React from 'react';
    import './App.css';
    import LikesButton from './components/LikesButtons'
    
    function App() {
        const tags = ['恐龙', '足球小子']
        return (
            <div className="App">
                {/*<NameCard name='viking' number={343442223} isHuman tags={tags}/>*/}
                <LikesButton />
            </div>
        );
    }
    
    export default App;
    

    然后看到点赞按钮已经出来了

    image-20200229105111897

    接下来如何改变点赞次数呢??

    react 处理事件和js处理DOM处理事件是类似的,不同的是事件绑定不是全小写的,而是驼峰式的,首先给按钮添加点击事件,点击之后,随便输出内容,如下代码所示:

    import React from 'react'
    
    class LikesButtons extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                likes: 0
            }
        }
    
        increaseLikes() {
            alert(1234);
            console.log(this);
        }
    
        render() {
            return (
                <div className="likes-button-componet">
                    <button type="button" className="btn btn-outline-primary btn-lg" onClick={this.increaseLikes}>
                        点赞 {this.state.likes}
                    </button>
                </div>
            )
        }
    }
    
    export default LikesButtons;
    

    查看代码可以发现,按钮点击的回调事件是在类里面的increaseLikes方法,但是点击按钮之后,该方法里面的this却被输出undefined,但是按钮点击之后需要修改this.state.likes属性,所以需要将this绑定到increaseLikes方法里面,如下代码,使用bind()方法,还可以使用ES6的箭头函数来解决这个问题,第一种方式容易理解一点。

    1.this.increaseLikes = this.increaseLikes.bind(this);
    
     2.<button type="button" className="btn btn-outline-primary btn-lg"
                            onClick={() => {
                                this.increaseLikes()
                            }}>
    
    import React from 'react'
    
    class LikesButtons extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                likes: 0
            }
            this.increaseLikes = this.increaseLikes.bind(this);
        }
    
        increaseLikes() {
            alert(1234);
            console.log(this);
        }
    
        render() {
            return (
                <div className="likes-button-componet">
                    <button type="button" className="btn btn-outline-primary btn-lg" onClick={this.increaseLikes}>
                        点赞 {this.state.likes}
                    </button>
                </div>
            )
        }
    }
    
    export default LikesButtons;
    

    点击按钮之后,发现输出的this,已经是LikesButtons类本身了。

    接下来就可以点击一次,对like.state属性增加1,如下代码:

    import React from 'react'
    
    class LikesButtons extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                likes: 0
            }
            this.increaseLikes = this.increaseLikes.bind(this);
        }
    
        increaseLikes() {
            this.setState({
                likes: ++this.state.likes
                }
            )
        }
    
        render() {
            return (
                <div className="likes-button-componet">
                    <button type="button" className="btn btn-outline-primary btn-lg" onClick={this.increaseLikes}>
                        点赞 {this.state.likes}
                    </button>
                </div>
            )
        }
    }
    
    export default LikesButtons;
    

    运行之后发现已经可以实现 每点击一次按钮,点赞次数+1的效果 了。

    这里需要强调的一点是state值不能直接修改,唯一的一种方式就是利用setState()方式让其发生变化。

    1.2.3.4 React生命周期

    一个组件的声明周期有:

    组件初始化

    组件更新

    组件卸载

    image-20200229112223245

    上面那张图详细的描述了组件的生命周期过程,创建、更新和卸载。接下来通过一个电子钟表来理解整个生命周期,在components文件夹新建DigitalClock.js文件,先声明一个静态的钟表,代码如下

    import React from 'react'
    
    class DigitalClock extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                date: new Date()
            }
        }
    
        render() {
            return (
                <div className="digital-clock-componet jumbotron">
                    <h1>{this.state.date.toLocaleTimeString()}</h1>
                </div>
            )
        }
    }
    export default DigitalClock
    

    然后在APP.js文件里面引入DigitalClock组件,代码如下:

    import React from 'react';
    import './App.css';
    import NameCard from './components/NameCard';
    import LikesButton from './components/LikesButtons'
    import DigitalClock from './components/DigitalClock'
    
    function App() {
        const tags = ['恐龙', '足球小子']
        return (
            <div className="App">
                {/*<NameCard name='viking' number={343442223} isHuman tags={tags}/>*/}
                <LikesButton />
                <DigitalClock/>
            </div>
        );
    }
    
    export default App;
    
    

    运行发现已经可以正常显示当前的时间了,如下所示:

    上午11:41:10

    然后考虑将钟表动态变化,对其添加生命周期的方法,componentDidMount方法和componentWillUnmount方法,前者是在组件创建的时候被调用,后者是在组件卸载的时候被调用。修改的DigitalClock.js代码如下,componentDidMount()方法启动一个定时器,然后每隔1000ms被调用一次更改state.date的值,之后卸载组件的时候要记得在方法里面利用clearInterval方法销毁定时器。运行之后,钟表就可以正常运行了。

    import React from 'react'
    
    class DigitalClock extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                date: new Date()
            }
        }
    
        componentDidMount() {
            this.timer = setInterval(() => {
                this.setState({
                    date: new Date()
                })
            }, 1000)
        }
    
        componentWillUnmount() {
            clearInterval(this.timer)
        }
    
        render() {
            return (
                <div className="digital-clock-componet jumbotron">
                    <h1>{this.state.date.toLocaleTimeString()}</h1>
                </div>
            )
        }
    }
    
    export default DigitalClock
    
    

    声明周期里面还有一个方法componentDidUpdate()方法,在组件更新的时候会被调用,

    将时间间隔变长一点,如下代码

    import React from 'react'
    
    class DigitalClock extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                date: new Date()
            }
        }
    
        componentDidMount() {
            this.timer = setInterval(() => {
                this.setState({
                    date: new Date()
                })
            }, 3000)
        }
    
        componentDidUpdate(currentProps, currentState) {
            console.log(currentState)
        }
    
        componentWillUnmount() {
            clearInterval(this.timer)
        }
    
        render() {
            return (
                <div className="digital-clock-componet jumbotron">
                    <h1>{this.state.date.toLocaleTimeString()}</h1>
                </div>
            )
        }
    }
    
    export default DigitalClock
    

    输出的内容如下,可以看出componentDidUpdate()在调用setState()方法更新组件的时候会被调用。

    {date: Sat Feb 29 2020 12:05:00 GMT+0800 (中国标准时间)}
    DigitalClock.js:20 {date: Sat Feb 29 2020 12:05:02 GMT+0800 (中国标准时间)}
    DigitalClock.js:20 {date: Sat Feb 29 2020 12:05:05 GMT+0800 (中国标准时间)}
    DigitalClock.js:20 {date: Sat Feb 29 2020 12:05:08 GMT+0800 (中国标准时间)}

    1.2.3.4.1 React生命周期实战

    声明周期方法在特定的时刻被调用,声明周期方法又叫勾子。声明周期方法也可以理解为回调函数,是react已经设计好的。

    声明周期流程:

    第一次初始化选渲染显示:ReactDOM.render
    constructor() :创建对象初始化state
    componentWillMount (): 将要插入回调,组件加载的时候回调
    render() :用于插入虚拟DOM回调
    componentDidMout():已经插入DOM回调
    每次更新state:this.setState()
    componentWillUpdate(): 将要更新回调
    render():更新(重新渲染)
    componentDidUpdate():已经更新回调
    移除组件:ReactDOM.unmountComponentAtNode(dom)
    componentWillUnmount() 组件将要被移除的时候回调

    声明周期流程:**

    第一次初始化选渲染显示:ReactDOM.render

    constructor() :创建对象初始化state

    componentWillMount (): 将要插入回调,组件加载的时候回调

    render() :用于插入虚拟DOM回调

    componentDidMout():已经插入DOM回调

    每次更新state:this.setState()

    componentWillUpdate(): 将要更新回调

    render():更新(重新渲染)

    componentDidUpdate():已经更新回调

    移除组件:ReactDOM.unmountComponentAtNode(dom)

    componentWillUnmount() 组件将要被移除的时候回调

    从下面的一个例子感受一下声明周期整个过程,文字的颜色动态变化(通过定时器),点击按钮之后删除组件,要注意删除组件的时候要同步删除定时器组件,不然会导致内存泄漏,定时器在组件删除之后仍然会执行(组件的不同方法里面想看到同一个东西,可以放到组件对象里面(this.intervalId),如下面的代码按钮点击之后定时器没有被清除,会造成内存泄漏,把声明周期的方法输出,看看控制台可以帮我们更容易理解声明周期的方法

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    class LifeStyle extends React.Component {
        constructor(props) {
            console.log("constructor");
            super(props);
            this.state = {
                opacity: 1
            }
            this.click = this.click.bind(this);
        }
    
        componentDidMount() {
            console.log("componentDidMount");
        }
        componentWillMount() {
            console.log("componentWillMount");
            setInterval(function () {
                let { opacity } = this.state;
                opacity -= 0.1
                if (opacity <= 0) {
                    opacity = 1;
                }
                console.log("定时器更新")
                this.setState({ opacity })
            }.bind(this), 200)
        }
    
        componentWillUpdate() {
            console.log("componentWillUpdate");
        }
    
        componentDidUpdate() {
            console.log("componentDidUpdate");
        }
    
        click() {
            ReactDOM.unmountComponentAtNode(document.getElementById('root'));
        }
    
        render() {
            console.log("render");
            const { opacity } = this.state;
            return (
                <div>
                    <h2 style={{ opacity: opacity }}> {this.props.msg} </h2>
                    <button onClick={this.click}>删除组件</button>
                </div>
            )
        }
    }
    export default LifeStyle;
    

    控制台输入内容如下:

    constructor
    componentWillMount
    render
    componentDidMount
    定时器更新
    componentWillUpdate
    render
    componentDidUpdate
    定时器更新
    componentWillUpdate
    render
    componentDidUpdate
    componentWillUnmount
    LifeStyle.jsx:25 定时器更新
    

    最后我们需要在组件卸载的时候清除定时器就可以了,定时器在组件删除之后仍然会执行(组件的不同方法里面想看到同一个东西,可以放到组件对象里面(this.intervalId)

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    class LifeStyle extends React.Component {
        constructor(props) {
            console.log("constructor");
            super(props);
            this.state = {
                opacity: 1
            }
            this.click = this.click.bind(this);
        }
    
        componentDidMount() {
            console.log("componentDidMount");
        }
        componentWillMount() {
            console.log("componentWillMount");
            this.intervalId = setInterval(function () {
                let { opacity } = this.state;
                opacity -= 0.1
                if (opacity <= 0) {
                    opacity = 1;
                }
                console.log("定时器更新")
                this.setState({ opacity })
            }.bind(this), 200)
        }
    
        componentWillUpdate() {
            console.log("componentWillUpdate");
        }
    
        componentDidUpdate() {
            console.log("componentDidUpdate");
        }
    
        componentWillUnmount() {
            console.log("componentWillUnmount");
        }
        click() {
            ReactDOM.unmountComponentAtNode(document.getElementById('root'));
            clearInterval(this.intervalId);
        }
    
        render() {
            console.log("render");
            const { opacity } = this.state;
            return (
                <div>
                    <h2 style={{ opacity: opacity }}> {this.props.msg} </h2>
                    <button onClick={this.click}>删除组件</button>
                </div>
            )
        }
    }
    export default LifeStyle;
    

    这样我们就可以得到动态变化的文字,然后点击删除组件,该组件就会被清除。

    1.2.3.5 React表单

    表单组件分为下面2种:

    受控组件:表单项输入数据能 自动收集成状态(state和setState方式存储数据)

    非受控组件:ref={input => this.input = input},需要时手动读取表单输入框的值

    表单元素和其他DOM元素的区别,表单元素有自己的状态,react负责渲染元素,只有react操作的元素称为受控组件,接下来通过实现留言框来理解表单元素。首先,在components文件夹下新建CommentBox.js文件,先生成一个静态的留言框,代码如下:

    import  React from 'react'
    
    class CommentBox extends  React.Component {
        constructor(props) {
            super(props)
            this.state = {
                value: ''
            }
        }
    
        render() {
            return (
                <form className="p-5">
                    <div className="form-group">
                        <label>留言内容</label>
                        <input type="text" className="form-group" placeholder="请输入内容"
                               value={this.state.value} />
                    </div>
                    <button type="submit" className="btn btn-primary">
                        留言
                    </button>
                </form>
            )
        }
    }
    
    export default CommentBox
    

    将CommentBox组件引入到App.js里面,效果如下图所示:

    image-20200229150112981

    发现现在其实还不能输入浏览内容,this.state.value是其初始值,要通过setState方法来改变表单元素的内容,

    添加onChange()方法来修改留言框的内容,代码如下所示:

    import React from 'react'
    
    class CommentBox extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                value: ''
            }
            this.handleChange = this.handleChange.bind(this)
        }
    
        handleChange(event) {
            this.setState({
                value: event.target.value
            })
        }
    
        render() {
            return (
                <form className="p-5">
                    <div className="form-group">
                        <label>留言内容</label>
                        <input type="text" className="form-group" placeholder="请输入内容"
                               onChange={this.handleChange} value={this.state.value}/>
                    </div>
                    <button type="submit" className="btn btn-primary">
                        留言
                    </button>
                </form>
            )
        }
    }
    
    export default CommentBox
    

    运行之后发现输入框已经可以输入内容了,最后对表单添加onSubmit方法来提交表单的元素,通过onSubmit方法提交表单元素之后一般都会跳转,所以要使用event.preventDefault()方式来防止浏览器跳转。

    import React from 'react'
    
    class CommentBox extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                value: ''
            }
            this.handleChange = this.handleChange.bind(this)
            this.handleSubmit = this.handleSubmit.bind(this)
        }
    
        handleChange(event) {
            this.setState({
                value: event.target.value
            })
        }
    
        handleSubmit(event) {
            alert(this.state.value);
            event.preventDefault()
        }
    
        render() {
            return (
                <form className="p-5" onSubmit={this.handleSubmit}>
                    <div className="form-group">
                        <label>留言内容</label>
                        <input type="text" className="form-group" placeholder="请输入内容"
                               onChange={this.handleChange} value={this.state.value}/>
                    </div>
                    <button type="submit" className="btn btn-primary">
                        留言
                    </button>
                </form>
            )
        }
    }
    
    export default CommentBox
    

    运行之后,发现已经可以通过留言按钮提交了。但是查看上面的代码发现每次当受控组件的状态发生变化时,都要去通过setState()方式来改变对应的属性,这种方式比较麻烦。这时候,我们考虑用非受控组件来替代受控组件了,删除之前的setState()方法,然后通过ref的方式来替代,如下代码所示,通过textInput来直接获取DOM元素的值,非受控组件看起来更加简洁。

    import React from 'react'
    
    class CommentBox extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                value: ''
            }
            this.handleSubmit = this.handleSubmit.bind(this)
        }
    
        handleSubmit(event) {
            alert(this.textInput.value);
            event.preventDefault()
        }
    
        render() {
            return (
                <form className="p-5" onSubmit={this.handleSubmit}>
                    <div className="form-group">
                        <label>留言内容</label>
                        <input type="text" className="form-group" placeholder="请输入内容"
                               ref={(textInput) => {
                                   this.textInput = textInput
                               }}/>
                    </div>
                    <button type="submit" className="btn btn-primary">
                        留言
                    </button>
                </form>
            )
        }
    }
    
    export default CommentBox
    

    ref机制,因为react不允许我们直接操作DOM元素,ref机制提供给我们一种直接调用DOM元素元素API方式,其中传入的textInput就是input 元素本身(javascript元素),当react加载的时候,ref里面的代码会被自动执行,textInput元素就被保存到this.textInput属性中去,通过this.textInput我们就可以直接操作DOM元素了,通过this.textInput就可以获取该javascript元素,然后调用相应的API方法了,比如this.this.textInput().focus()方法等等了。

    1.2.4 虚拟DOM与DOM diff算法

    image-20200320170827165

    1.3 React的综合实例

    image-20200229165319560

    1.3.1 留言本实例

    1.3.1.1 留言本实例分析

    多个组件分享同一份数据,多数情况下 ,将共享的数据状态提升到最近的父组件里面管理。在React应用中,对于任何可变数据,理应只有一个单一数据源,而且应该在应用中保持自上而下的单向数据流,下图中的箭头都表示从父组件传递到子组件中去。

    image-20200229160353030

    如上图所示,提升的变量(comments)提升到父组件App.js文件中,在子组件(CommentBox.js)中回调函数addComment()方法来改变提升的变量值。

    image-20200229165319560

    1.3.1.2 留言本实例编码

    当一个组件没有生命周期,没有state的时候,可以将其写成一个function的形式。在components文件目录下面新建CommentList.js文件,该文件只涉及到comments数组的获取,不涉及到数组的变化,所以将其写成一个函数的形式,代码如下:

    import React from 'react'
    
    const CommentList = ( { comments } ) => {
        return (
            <div className="comment-list-component">
                <label>评论列表</label>
                <ul className="list-group mb-3">
                    {
                        comments.map((comment, index) =>
                            <li key={index} className="list-group-item"> {comment} </li>)
                    }
                </ul>
            </div>
        )
    }
    
    export default CommentList
    

    然后在App.js定义comments数组变量,并将其传给CommentList组件,代码如下:

    import React from 'react';
    import './App.css';
    import CommentList from './components/CommentList'
    
    class App extends React.Component {
      constructor(props) {
          super(props)
          this.state ={
              comments: ['this is my first reply']
          }
      }
        render() {
            return (
                <div className="App">
                    <CommentList comments={this.state.comments} />
                </div>
            );
        }
    
    }
    
    export default App;
    

    运行之后的效果如下所示:已经可以显示评论列表了,如下所示:

    评论列表

    • this is my first reply

    之后修改CommentBox.js文件,添加评论数,如下图所示。这里的评论数只能从App.js中读取。

    import React from 'react'
    
    class CommentBox extends React.Component {
        constructor(props) {
            super(props)
            this.handleSubmit = this.handleSubmit.bind(this)
        }
    
        handleSubmit(event) {
            alert(this.textInput.value);
            event.preventDefault()
        }
    
        render() {
            return (
                <form className="p-5" onSubmit={this.handleSubmit}>
                    <div className="form-group">
                        <label>留言内容</label>
                        <input type="text" className="form-group" placeholder="请输入内容"
                               ref={(textInput) => {
                                   this.textInput = textInput
                               }}/>
                    </div>
                    <button type="submit" className="btn btn-primary">
                        留言
                    </button>
                    <p>已有{this.props.commentLength}条评论</p>
                </form>
            )
        }
    }
    
    export default CommentBox
    

    修改的App.js文件代码如下:

    import React from 'react';
    import './App.css';
    import NameCard from './components/NameCard';
    import LikesButton from './components/LikesButtons'
    import DigitalClock from './components/DigitalClock'
    import CommentBox from './components/CommentBox'
    import CommentList from './components/CommentList'
    
    class App extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                comments: ['this is my first reply']
            }
        }
    
        render() {
            const {comments} = this.state;
            return (
                <div className="App">
                    {/*/!*<NameCard name='viking' number={343442223} isHuman tags={tags}/>*!/*/}
                    {/*<LikesButton  />*/}
                    {/*<DigitalClock />*/}
                    <CommentList comments={comments}/>
                    <CommentBox commentLength={comments.length}/>
                </div>
            );
        }
    
    }
    
    export default App;
    

    最后给留言按钮添加点击事件,每次点击留言之后,这里我们需要给表单添加回调函数将添加的留言内容传到App.js文件中,然后更新comments数组,CommentsBox.js文件代码如下:

    import React from 'react'
    
    class CommentBox extends React.Component {
        constructor(props) {
            super(props)
            this.handleSubmit = this.handleSubmit.bind(this)
        }
    
        handleSubmit(event) {
            alert(this.textInput.value)
            this.props.onAddComment(this.textInput.value)
            event.preventDefault()
        }
    
        render() {
            return (
                <form className="p-5" onSubmit={this.handleSubmit}>
                    <div className="form-group">
                        <label>留言内容</label>
                        <input type="text" className="form-group" placeholder="请输入内容"
                               ref={(textInput) => {
                                   this.textInput = textInput
                               }}/>
                    </div>
                    <button type="submit" className="btn btn-primary">
                        留言
                    </button>
                    <p>已有{this.props.commentLength}条评论</p>
                </form>
            )
        }
    }
    
    export default CommentBox
    

    App.js收到回调函数的comment内容之后,通过setState()方法更新comments数组内容,代码如下,给数据添加元素使用了ES6的语法来实现。

    import React from 'react';
    import './App.css';
    import NameCard from './components/NameCard';
    import LikesButton from './components/LikesButtons'
    import DigitalClock from './components/DigitalClock'
    import CommentBox from './components/CommentBox'
    import CommentList from './components/CommentList'
    
    class App extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                comments: ['this is my first reply']
            }
            this.addComment = this.addComment.bind(this)
        }
    
        addComment(comment) {
            this.setState({
                comments: [...this.state.comments, comment]
            })
        }
    
        render() {
            const {comments} = this.state;
            return (
                <div className="App">
                    <CommentList comments={comments}/>
                    <CommentBox 
    					commentLength={comments.length}
                        onAddComment={this.addComment}
                    />
                </div>
            );
        }
    
    }
    
    export default App;
    

    大功告成了,每点击一次留言按钮,评论列表会更新用户的留言内容,评论数也可以更新。

    image-20200229165137405

    1.3.2 Context介绍

    props属性是自上而下传递的;如果中间有好几个组件,需要一层层传递下去;

    Context提供了在组件中共享此类值的方法。

    context设计的目的是共享那些对于组件来说全局的数据,不要仅仅为了避免在几个层级下的组件传递props而使用context。

    下面通过一个样式选择器来理解context,当点击浅色主题时,变成浅色,当点击深色主题时,变成深色。

    image-20200229170219405

    接下来分析具体的编码过程,在src目录下添加theme-context.js文件,因为context不是一个组件,而是一个对象。theme-context.js代码如下

    import React from 'react'
    
    const ThemeContext=React.createContext()
    export default ThemeContext
    

    在App.js引用ThemeContext,利用React.createContext()方法创建contextThemeContext对象的时候,会得到2个对象。只要使用context值的节点被Provider组件包裹,就可以把对应的数据传给context,通过Consumer组件获取传入的值。修改App.js文件代码如下,通过ThemeContext.Provider传递theme值。

    import React from 'react';
    import './App.css';
    import NameCard from './components/NameCard';
    import LikesButton from './components/LikesButtons'
    import DigitalClock from './components/DigitalClock'
    import CommentBox from './components/CommentBox'
    import CommentList from './components/CommentList'
    import ThemeContext from './theme-context'
    
    const themes = {
        light: {
            classnames: 'btn btn-primary',
            bgColor: '#eeeeee',
            color: '#000'
        },
        dark: {
            classnames: 'btn btn-light',
            bgColor: '#222222',
            color: '#fff'
        },
    }
    
    class App extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                comments: ['this is my first reply']
            }
            this.addComment = this.addComment.bind(this)
        }
    
        addComment(comment) {
            this.setState({
                comments: [...this.state.comments, comment]
            })
        }
    
        render() {
            const {comments} = this.state;
            return (
                <ThemeContext.Provider value={themes.light}>
                    <div className="App">
                        <a href="#theme-switcher"
                           className="btn btn-light">
                            浅色主题
                        </a>
                        <a href="#theme-switcher"
                           className="btn btn-secondary">
                            深色主题
                        </a>
                    </div>
                </ThemeContext.Provider>
            );
        }
    }
    
    export default App;
    

    在components目录下,新建一个文件ThemedBar.js,代码如下,通过ThemeContext.Consumer组件获取传入的theme值。

    import React from 'react'
    import ThemeContext from '../theme-context'
    
    const ThemedBar = () => {
        return (
            <ThemeContext.Consumer>
                {
                    theme => {
                        console.log(theme)
                    }
                }
            </ThemeContext.Consumer>
        )
    }
    
    export default ThemedBar
    

    在App.js文件引入ThemedBar组件,如下代码所示,在控制台中已经可以成功获取theme属性了,如下图所示;

    import React from 'react';
    import './App.css';
    import ThemeContext from './theme-context'
    import ThemedBar from "./components/ThemedBar";
    
    const themes = {
        light: {
            classnames: 'btn btn-primary',
            bgColor: '#eeeeee',
            color: '#000'
        },
        dark: {
            classnames: 'btn btn-light',
            bgColor: '#222222',
            color: '#fff'
        },
    }
    
    class App extends React.Component {
        constructor(props) {
            super(props) 
        }
    
        render() {
            return (
                <ThemeContext.Provider value={themes.light}>
                    <div className="App">
                        <a href="#theme-switcher"
                           className="btn btn-light">
                            浅色主题
                        </a>
                        <a href="#theme-switcher"
                           className="btn btn-secondary">
                            深色主题
                        </a>
                    </div>
                    <ThemedBar />
                </ThemeContext.Provider>
            );
        }
    }
    
    export default App;
    
    
    1. {classnames: "btn btn-primary", bgColor: "#eeeeee", color: "#000"}

    最后我们来实现本章节最上面的图片,修改ThemedBar.js文件内容,代码如下

    import React from 'react'
    import ThemeContext from '../theme-context'
    
    const ThemedBar = () => {
        return (
            <ThemeContext.Consumer>
                {
                    theme => {
                       return (
                           <div className="alert mt-5" style={ {backgroundColor:theme.bgColor,color:theme.color} }>
                               样式区域
                               <button className={theme.classnames}>
                                   样式按钮
                               </button>
                           </div>
                       )
                    }
                }
            </ThemeContext.Consumer>
        )
    }
    
    export default ThemedBar
    

    点击运行之后,效果如下图所示,传入的浅色样式已经被成功应用。

    image-20200229175303029

    为了让深色主题和浅色主题按钮点击生效,我们来给按钮添加事件,代码如下所示:

    import React from 'react';
    import './App.css';
    import ThemeContext from './theme-context'
    import ThemedBar from "./components/ThemedBar";
    
    const themes = {
        light: {
            classnames: 'btn btn-primary',
            bgColor: '#eeeeee',
            color: '#000'
        },
        dark: {
            classnames: 'btn btn-light',
            bgColor: '#222222',
            color: '#fff'
        },
    }
    
    class App extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                theme: 'light'
            }
            this.changeTheme = this.changeTheme.bind(this)
        }
    
        changeTheme(theme) {
            this.setState({
                theme: theme
            })
        }
    
        render() {
            const {comments} = this.state;
            return (
                <ThemeContext.Provider value={themes[this.state.theme]}>
                    <div className="App">
                        <a href="#theme-switcher"
                           className="btn btn-light"
                           onClick={() => {
                               this.changeTheme('light')
                           }}
                        >
                            浅色主题
                        </a>
                        <a href="#theme-switcher"
                           className="btn btn-secondary"
                           onClick={() => {
                               this.changeTheme('dark')
                           }}
                        >
                            深色主题
                        </a>
                    </div>
                    <ThemedBar/>
                </ThemeContext.Provider>
            );
        }
    }
    
    export default App;
    

    点击运行之后,大功告成,点击深色主题和浅色主题就可以切换对应的主题了。

    1.4 组件间通信

    通过props传递

    共同的数据放在父组件,将特有的数据放在自己组件内部

    通过props可以传递一般数据和函数数据,只能一层一层传递

    一般数据--》父组件传递数据给子组件--》子组件读取数据

    函数数据--》子组件传递数据给父组件--》子组件调用函数

    react有个弊端就是只能父、子组件传递,兄弟组件不能传递数据,可以考虑引入pubsub-js方式订阅、发布消息的方式来实现兄弟组件之间的通信。发布消息是在组件的事件函数里面,订阅消息可以考虑在组件初始化的时候订阅,然后接受发布的消息,订阅消息的回调函数可以考虑用箭头函数。

    回调函数用箭头函数,可以解决this绑定的问题。

    1.5 react-router

    整个页面只有一个完整的页面

    点击页面中的链接不会刷新页面,本身也不会向服务器发送请求

    当点击路由链接时,只会做页面的局部更新

    数据都需要通过ajax请求获取,并在前端异步展现

    路由的理解:

    一个路由就是一个映射关系(key:value)

    key为路由路径,value可能是function(后台)/component(前台)

    后台路由:node服务器端路由,value是function,用来处理客户端提交的请求并返回一个响应数据。

    前台路由:浏览器端路由,value是component,当请求的是路由path时,浏览器端前没有发送http请求,但界面会更新显示对应的组件

    后台路由

    注册路由:router.get(path,function(req,res))

    当node接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据。

    前端路由

    注册路由:

    当浏览器的hash变成#About时,前端路由组件就会变成About组件

  • 相关阅读:
    .NETframework的EF框架学习报错之datetime 数据类型
    String...的用法
    存储过程从入门到熟练(c#篇)
    售前如何做好产品演示
    华为演讲培训售前人员重点学习
    report services 报表开发和部署,集成到解决方案中 全解析
    在Asp.net用C#建立动态Excel(外文翻译)
    NET(C#)连接各类数据库集锦
    在SourceForge.net上如何使用TortoiseCVS
    用C#实现在线升级
  • 原文地址:https://www.cnblogs.com/zdjBlog/p/12384417.html
Copyright © 2020-2023  润新知