关于React的第一篇笔记

目录

1.入门

1.1开发环境

1.1.1在线开发环境

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

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:\zdj\zdj-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,比较简单,就是花括号的概念,在花括号里面可以添加任何的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 jsx的编译

jsx利用babel编译解析,是React.createElement() 的语法糖。

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 React表单

表单元素和其他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

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;

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

猜你喜欢

转载自www.cnblogs.com/zdjBlog/p/12384417.html