使用webpack配置一个react开发环境以及react的使用

1、react简介

react 起源于facebook用来架设自己的insgram网站,后在2013年开源出来。因其独特的设计思想,属于前端框架的革命性创新,性能出众,代码逻辑简单。受到越来越多的关注,目前web开发的主流工具之一。
angular 于2009年起源于谷歌,一款mvc框架,起初并不支持组件化开发。

需要清楚的两个概念,库和框架

library(库):提供了可以直接调用的实现某些功能的特定的api,特点,小而巧。可以比较方便的从一个库切换到另一个库,代码几乎不变。
Framework(框架):提供了一整套的解决方案,特点大而全。一个项目想从一个框架切换到另外一个框架,就需要重构,较困难。

2、目前的三大主流框架

Angular.js:出现较早,学习曲线陡峭,angular1比较麻烦,angular2-angular5进行了框架的改革,增加了组件化开发的概念,开始支持typescript编程。

Vue.js:当下最火的前端框架,由国内团队开发,文档相对来说更友好一些。

React.js:前端最流行的一款框架,设计优秀,性能强悍。

3、React与Vue的一些对比

组件化方面

说到组件化要明白组件化和模块化的含义和差别,以及理解组件化开发的优点。

模块化:更多的是从代码的角度来分析的,将一些实现某些特定功能的代码进行封装,抽离为单个模块,在需要的地方直接调用。便于项目的维护和开发。

组件化:是从ui的角度来分析的,将一些可以复用的元素、模板抽离为单独的组件,同样也便于项目的维护和开发。

进行组件化开发,便于随着项目的规模的增大,可以将已有的组件多次复用,方便快速的将组件拼接为一个完整的页面。

vue是如何来实现组件化的?
vue中带.vue后缀的文件就可以视为一个组件,它包含三部分:template(结构)、script(行为)、css(样式),通常在vue中通过创建.vue后缀的文件来创建一个组件。

React是如何实现组件化的?
尽管react中有组件化的概念,但并没有像vue中这样的组件模板文件。react一切都是以js来实现的,因此react的学习,必要的前提是有扎实的js基础,知道es6、es7的相关内容。

开发团队方面

React由facebook的前端官方团队进行维护和更新,其技术实力更雄厚。
Vue由以尤雨溪为主的团队进行开发和维护。

社区方面

React开源较早,社区比较强大,一些开发中出现的问题,最优的解决方案,文档,博客一类可以很方便的找到,可以给与开发者很大的帮助。
Vue出现稍晚,社区相对于react来说小一些。

移动app开发体验方面

vue结合weex,提供了移动端app开发的体验,目前项目的案列多是阿里旗下的一下app在使用。

react 结合reactnative 提供了无缝迁移到移动端app的开发体验,是目前较成熟的应用较广的技术。

4、学习react原因

同angular相比,react设计巧妙,一切基于js并且实现了组件化开发。
开发团队强大稳定,不必担心断更的问题。
社区强大,很多问题在社区能找到对应的解决方案
提供了无缝转到reactnative上的开发体验,
很多中大型企业的前端技术选型采用的是react。

5、react中的几个核心的概念

虚拟Dom

Dom的本质

浏览器中的概念,以js对象来表示页面的元素,提供了操作Dom对象的API。

什么是react中的虚拟Dom

以js对象来模拟页面上的dom元素以及dom元素的嵌套关系。

虚拟Dom的目的

实现页面中,Dom元素的高效更新。

Dom和虚拟Dom的区别

Dom:浏览器中的提供的概念,用js对象表示页面上的元素,并提供了操作元素的API
虚拟Dom:框架当中的概念,以js模拟Dom元素和嵌套关系
目的:实现页面的高效更新。

diff算法

tree diff 、component diff、element diff
tree diff :新旧两棵Dom树逐层对比的过程就是tree diff ,当整个Dom树逐层对比完毕,那么需要更新的元素,自然而然能够被找到。

component diff :进行tree diff 时,每一层级中组件的对比称为component diff。
如果对比前后,组件类型相同,则暂时认为组件不需要更新。
如果对比前后,组件类型不同,则需要创建新组件同时移除旧组件,并将新组件追加到页面。

element diff :在进行component diff 时 如果组件的类型相同,那么就再进行组件中元素的对比,这叫做element diff.


diff算法概念图

虚拟Dom存在的必要性:


虚拟Dom存在的必要性
虚拟Dom存在的必要性

以js来模拟一个Dom对象


js模拟Dom对象

6、创建一个基本的webpack4.x的项目为后续创建react项目最基础

a.创建一个空文件夹作为项目的目录(最好是不包含大写英文字母,否则后面在使用webpack-dev-server来运行时会有一堆警告出现在控制台)
b.命令行运行该文件夹,执行npm init -y 来初始化项目
c.在项目根目录下创建src源代码目录和dist产品目录
d.在src目录中创建index.html首页模板,以及主入口文件index.js
f.安装webpack 执行 npm/cnpm install webpack webpack-cli -D
g.webpack4.x版本提供了约定大于配置的概念,目的是为了尽量减少配置文件的体积。其中默认约定了以下内容:
打包的入口文件:src目录下的index.js
打包的输出文件:dist目录下的main.js
4.x版本新增了必选项mode,值为development或production.


一个基本的webpack4.x版本项目架构

此外,webpack4.x中常用的插件 webpack-dev-server和html-webpack-plugin
安装完之后需要进行相关配置,这样可以让项目更高效的运行,可以热更新。
安装webpack-dev-server:npm/cnpm install webpack-dev-server -D

然后在配置文件中做如下配置
package.json文件中配置webpack-dev-server

配置完毕后,就可以通过npm run dev来运行
--open(自动打开浏览器) --port 设置端口号 --hot 热更新 --host 设置主机名
安装html-webpack-plugin设置模板文件

npm/cnpm install html-webpack-plugin -D
这样一个基本的webpack4.x的项目便构建起来。
在webpack.config.js配置过后来使用 如下图


html-webpack-plugin的配置

7、在项目中开始使用react

a.安装react 和react-dom;npm/cnpm install react react-dom -S
react 是用来创建虚拟dom结构的,创建组件,组件的生命周期都在这个包里面。
react-dom是用来进行操作dom的,主要的应用场景,是ReactDom.render()
b.在index.html页面来创建容器,将来创建的虚拟dom结构都会被渲染到这个指定的容器。
c.在主入口文件index.js中引入安装的react的资源包
import React from 'react'
import ReactDom from 'react-dom
d.在主入口文件创建虚拟dom元素
React.createElement(n1,n2,n3)
n1:字符串类型参数,代表需要创建的dom元素标签名
n2:对象类型的参数 代表创建元素的属性节点
n3:子节点 包含文本节点
const myDom = React.createElement('h1',{id:'Box'},'我是一个h1标签')
e.渲染虚拟dom元素
ReactDom.render(myDom,document.getElementById('app'));
包含两个参数:需要渲染的dom元素,以及制定的dom容器(一个dom对象)。

8、Jsx语法

jsx语法就是符合xml规范的js语法,语法格式相对html来说要严格的多。
使用jsx语法可以在js中用写html标记语言的方式来创建虚拟dom元素,但是使用之前需要安装和配置对应的babel插件。
jsx语法的本质,依然是在运行的时候,通过babel转换成了React.createElement的形式

启用jsx语法的准备工作
a:安装babel插件

npm/cnpm install babel-core babel-loader@7 babel-plugin-transform-runtime -D
cnpm/npm install babel-preset-env babel-preset-stage-0 -D
安装能够识别和转化jsx语法的包 babel-preset-react
cnpm/npm install babel-preset-react -D

b:在根目录添加 .babelrc配置文件
{
"presets":["env","stage-0","react"],
"plugins":["transform-runtime"]
}
c:在webpack.config.js中添加babel-loader的配置项
module:{
   rules:[
      {test:/\.js|jsx$/,use:'babel-loader',exclude:/node_modules/}
]
}

有时候会出现安装的babel-core和babel-loader版本不兼容问题 babel-loader8.x的版本和babel-core6.x的版本会出现兼容问题,此时要么升级babel-core 要么降级babel-loader。

jsx语法的本质

并不是直接将jsx渲染到页面上,而是内部先转换成了createElement的形式然后又渲染的。

在jsx中混合写入js的表达式

jsx语法中要把代码写进{}中。
比如,jsx中进行的下面的操作
渲染数字
渲染字符串
渲染布尔值
为属性绑定值
渲染jsx元素
渲染jsx元素数组
将普通的字符串数组转化为jsx数组并且渲染到页面上 使用forEach或者map
在jsx中写注释的方式 {/* 注释 */}

为jsx中的元素添加class类名

要是用className来代替class;
用htmlFor替换label的for属性

其他

使用jsx语法创建Dom的时候,所有的Dom节点,必须有唯一一个根元素来进行包裹。
另外,由于jsx是遵循xml的规范,因此更加严格,标签必须成对出现。
当编译引擎在编译jsx代码的时候,如果遇到了<,那么会把它当做html代码来进行编译,遇到了{}就会把花括号内部的代码当做普通的js代码去编译。
图示


jsx语法使用基本介绍
jsx语法使用基本介绍

9、在react中如何创建组件

创建组件的第一种方式

使用构造函数来创建组件,如果要接收外界传递的数据,需要在构造函数的参数列表中使用props来接收,并必须向外return一个合法的jsx语法创建的虚拟Dom。

  • 构造函数创建组件
function Hello(){
      return <div>我是一个构造函数创造的组件</div>
}
  • 为组件传递数据
function Hello(props){
    //console.log(props)
    props.age = '100';
    //  无论是在vue还是在react, props永远都是只读的,无法被重新赋值。
    //console.log(props);
    //return null
    //如果一个组件中返回null,则表示此组件是空的,不会渲染,否则一定要返回一个合法的jsx语法的虚拟dom
return <div>我是一个构造函数创造的组件-----{props.age}-------{props.name}</div>
}
其他几个重要的点
  • 父组件与子组件之间数据的传递

  • 解构{...obj}的使用

  • 可以将组件封装到单独的文件中

  • 组件的名称必须是大写的

  • 将组件抽离为单独的jsx文件时,在jsx文件中不要忘记引入React

  • 通过配置webpack.config.js可以在引入组件的时候,省略掉.jsx的后缀名。
    如下图


    通过配置webpack来省略.jsx的后缀名
  • 通过配置webpack.config.js 可以使用别名来代替根目录
    如图


    配置别名@来代表根目录src

9、在react中如何创建组件

ES6中class关键字的作用
  • class关键字中构造器constructor的了解和基本使用
//console.log('123')
//构造函数创建一个类
function Person(name,age){
    this.name = name;
    this.age = age;
}
const p1= new Person('tom',25);
//直接挂载到构造函数的属性,称为静态属性
Person.info = '这是一个人的类';
console.log(p1);
//通过new出来的实例访问到的属性,称为实例属性
console.log(p1.name);
console.log(p1.age);
//访问静态属性不能通过实例,要通过构造函数
console.log(p1.info);//undefined
console.log(Person.info);

//分割线
console.log('---------------------------------------------')
//使用class关键字创建类
//class创建的类的{}中,只能写构造器,静态属性,实例属性,静态方法和实例方法
//另外class关键字内部本质还是构造函数的原理来实现的,这里的class可以看作是语法糖。
class Animal{
    //constructor是类的构造器 每一个类中都有一个构造器,如果没有人为指定构造器的话,那么类内部的构造器可以认为是个空构造器 constructor(){}
    //构造器的作用,当执行new一个实例的时候,必然优先执行构造器中的代码
    constructor(name,age){
        //实例属性
        this.name = name;
        this.age = age;
    }
    //在class内部创建静态属性,使用static关键字
    static info='这是一个动物的类'
   
}
const animal1 = new Animal('旺财',2);
console.log(animal1);
console.log(animal1.name);
console.log(animal1.age);
//访问静态属性同样要通过类
console.log(animal1.info);//undefined
console.log(Animal.info);
  • 实例属性和实例方法,静态属性和静态方法
function Person(name,age){
    //实例属性
    this.name = name;
    this.age = age;
}
//静态属性
Person.info = '这是个人的类';

//添加一个实例方法
Person.prototype.say = function(){
    console.log('你好')
}
//添加一个静态方法
Person.show = function(){
    console.log('这是静态方法')
}
const p1 = new Person('tom',20);
console.log(p1);
p1.say();//调用实例方法
//p1.show();//报错
Person.show();//调用静态方法

console.log('---------------------------------------------------')
//class创建类

class Animal{
    constructor(name,age){
    //实例属性
        this.name = name;
        this.age = age;
    }
    //静态属性
    static info = '这是动物类'
    //添加实例方法
    say(){
        console.log('动物类的实例方法')
    }
    //添加静态方法
    static show(){
        console.log('这是动物类的静态方法')
    }

}
const animal1 = new Animal('阿黄',3);
console.log(animal1);
//调用实例方法
animal1.say();
//调用静态方法
Animal.show();
  • 使用extends来实现继承
    创建两个类
//创建一个美国人的类
class American{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
}
const A1 = new American('jack',25);
console.log(A1);

//创建一个中国人的类
class Chinese{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
}
const C1 = new Chinese('林书豪',30);
console.log(C1);

为两个类创建父类并实现继承,以及子类自定义构造器时super的调用

//为类American和Chinese创建一个父类这里可以将其视为该两个类的原型对象 prototype
class Person{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    //添加实例方法
    say(){
        console.log('你好')
    }
}

//创建一个美国人的类
//class类中,可以使用extends实现子类继承父类 语法 class 子类 extends 父类 {}
class American extends Person{
}
const A1 = new American('jack',25);
console.log(A1);
A1.say();

//创建一个中国人的类
class Chinese extends Person{
    //如果此时不定义该类的construntor,那么该类的constructor其实就是父类的constructor,如果此处定义该类的constructor,那么需要使用函数super()
    constructor(name,age,IDcard){
        super(name,age)
        this.IDcard = IDcard
    }
    //3个问题
    //1.为什么要在constructor中使用super()?如果一个子类通过extends关键字继承父类,那么在该子类自定义constructor时,必须先调用super;
    //2.super是什么? super是一个函数,其本质是父类的构造器,子类的super()其实是父类构造器constructor在子类的引用。
    //3.调用了super后参数的传递?在定义类的constructor时可以为该类添加有别于父类实例属性的,
    //并且仅属于该类的实例属性(比如该例下的IDcard属性),但是为了保证同样拥有父类定义的实例属性,需要传必要的参数,如这里传递的name,age,否则对应属性就是undefined.

}
const C1 = new Chinese('林书豪',30,375435199389987654);
console.log(C1);
C1.say();
创建组件的第二种方式
类比以上,使用class关键字来创建react的组件
//如果要使用class来定义组件,必须让组件继承React.Component
class 组件名 extends React.Component{
          //  组件内部必须使用render函数
          render(){
                //render函数中必须返回符合jsx语法的虚拟Dom结构或null
                return <div>这是由class类创建的组件</div>
          }
}
class类创建的第一个react组件
import React from 'react'
//以上等同于 import React,{Component} from 'react'
import ReactDom from 'react-dom'

//class创建一个组件
class Onecomponent extends React.Component{
    //render函数用来渲染当前组件对应的虚拟Dom元素
    render(){
        //return null
        return <div>我是第一个用class类创建出来的react组件</div>
    }
}

//调用ReactDom render函数来渲染
ReactDom.render(
    <div>
        <Onecomponent></Onecomponent>
    </div>,document.getElementById('app'))

10、两种创建组件的方式的对比

使用class关键字创建的组件,有自己的私有数据和生命周期函数,而使用构造函数创建的组件,只有props,没有自己的私有数据和生命周期函数。
1、使用构造函数创建出来的组件是无状态组件。
2、使用class关键字创建出来的是有状态组件,在react中使用往往更多。
有状态组件和无状态组件的主要区别就是有无state属性和生命周期函数。
3、两种创建组件的方式的选择,什么时候使用?
如果一个组件需要有自己的私有数据,推荐使用class创建的有状态组件。
如果一个组件不需要有自己的私有数据,那么可以使用无状态组件,同时,因为构造函数创建的组件没有自己的私有数据和生命周期函数,运行效率相较于class创建的组件较高。
4、props和state的区别

  • props中的数据是外界传递的,而state中的数据是组件私有的(通过ajax获取到的数据一般都是私有数据)
  • props中的数据只读无法被修改,而state中的数据可读可写。
import React from 'react'
//以上等同于 import React,{Component} from 'react'
import ReactDom from 'react-dom'

//class创建一个组件
class Onecomponent extends React.Component{
    constructor(){
        super()
        //this.state在这里就相当于vue中的data(){return{}}
        this.state = {
            message:'我是class关键字创建的组件的私有数据'
        }
    }
    //render函数用来渲染当前组件对应的虚拟Dom元素
    render(){
        //return null
        //this.props.name = 'jack' 试图修改props的属性 会报错 。无论是使用class关键字创建的组件还是使用构造函数创建的组件 props都是只读的不可修改其属性。
        //class创建的组件的私有数据是可以被修改的,它是可读可写的
        this.state.message = '我被修改了'
        //class关键字创建的组件在使用外界的参数时,不需要接受,直接使用this.props.属性名即可访问,this代表的是当前组件创建的实例对象
        return <div>我是第一个用class类创建出来的react组件---{this.props.name}-----{this.props.age}------{this.state.message}</div>
    }
}
const user = {
    name:'tom',
    age:20
}
//调用ReactDom render函数来渲染
ReactDom.render(
    <div>
        <Onecomponent {...user}></Onecomponent>
    </div>,document.getElementById('app'))

11、使用组件创建一个评论列表

如图:
评论列表
import React from 'react'
import ReactDom from 'react-dom'


class Pinglun extends React.Component{
    constructor(){
        super()
        this.state = {
            TextList:[
                {id:0,name:'张三','words':'哈哈,沙发'},
                {id:1,name:'李四','words':'哈哈,板凳'},
                {id:2,name:'王五','words':'哈哈,凉席'},
                {id:3,name:'赵六','words':'哈哈,砖头'},
                {id:4,name:'田七','words':'哈哈,楼下山炮'}
            ]
        }
    }
    render(){
        return <div>
            <h3>这是一个评论列表</h3>
            {this.state.TextList.map(item=>
                <div key = {item.id}>
                    <h2>评论人:{item.name}</h2>
                    <p>评论内容:{item.words}</p>
                </div>
            )}
        </div>
    }
}
ReactDom.render(<div>
    <Pinglun></Pinglun>
</div>,document.getElementById('app'))

以上可以进一步处理如下(将列表结构抽离为一个无状态组件):

import React from 'react'
import ReactDom from 'react-dom'

//构造函数创建一个无状态组件应用于评论列表(组件的嵌套)
function Getpinglun(props){
    return <div>
                <h2>评论人:{props.name}</h2>
                <p>评论内容:{props.words}</p>
            </div>
}
class Pinglun extends React.Component{
    constructor(){
        super()
        this.state = {
            TextList:[
                {id:0,name:'张三','words':'哈哈,沙发'},
                {id:0,name:'李四','words':'哈哈,板凳'},
                {id:0,name:'王五','words':'哈哈,凉席'},
                {id:0,name:'赵六','words':'哈哈,砖头'},
                {id:0,name:'田七','words':'哈哈,楼下山炮'}
            ]
        }
    }
    render(){
        return <div>
            <h3>这是一个评论列表</h3>
            {this.state.TextList.map(item=><Getpinglun {...item} key={item.id}></Getpinglun>)}
        </div>
    }
}
ReactDom.render(<div>
    <Pinglun></Pinglun>
</div>,document.getElementById('app'))

进一步简化,将两个组件均抽离为单独的模块(两个单独的jsx文件),如下:
index.js文件

import React from 'react'
import ReactDom from 'react-dom'
//导入Pinglun组件
import Pinglun from './component/Pinglun'
//如果在webpack.config.js中配置了路径 那么还可以使用别名来引入 如import Pinglun from '@/component/Pinglun'


ReactDom.render(<div>
    <Pinglun></Pinglun>
</div>,document.getElementById('app'))

Pinglun.jsx文件

import React from 'react'
//导入组件Getpinglun
import Getpinglun from './Getpinglun'
//如果在webpack.config.js中配置了路径 那么还可以使用别名来引入 如import Getpinglun from '@/component/Getpinglun'

export default class Pinglun extends React.Component{
    constructor(){
        super()
        this.state = {
            TextList:[
                 {id:0,name:'张三','words':'哈哈,沙发'},
                {id:0,name:'李四','words':'哈哈,板凳'},
                {id:0,name:'王五','words':'哈哈,凉席'},
                {id:0,name:'赵六','words':'哈哈,砖头'},
                {id:0,name:'田七','words':'哈哈,楼下山炮'}
            ]
        }
    }
    render(){
        return <div>
            <h3>这是一个评论列表</h3>
            {this.state.TextList.map(item=><Getpinglun {...item} key={item.id}></Getpinglun>)}
        </div>
    }
}

Getpinglun.jsx文件

import React from 'react'
export default function Getpinglun(props){
     return <div>
                 <h2>评论人:{props.name}</h2>
                 <p>评论内容:{props.words}</p>
             </div>
}

12、为创建的组件添加样式

添加普通的内联样式 比如在Pinglun.jsx中
import React from 'react'
//导入组件Getpinglun
import Getpinglun from './Getpinglun'
//如果在webpack.config.js中配置了路径 那么还可以使用别名来引入 如import Getpinglun from '@/component/Getpinglun'

export default class Pinglun extends React.Component{
    constructor(){
        super()
        this.state = {
            TextList:[
                {id:0,name:'张三','words':'哈哈,沙发'},
                {id:0,name:'李四','words':'哈哈,板凳'},
                {id:0,name:'王五','words':'哈哈,凉席'},
                {id:0,name:'赵六','words':'哈哈,砖头'},
                {id:0,name:'田七','words':'哈哈,楼下山炮'}
            ]
        }
    }
    render(){
        return <div>
            {/* jsx语法中如果想加入行内样式那么应该照如下语法来写,属性值不是数字的要加引号,数字不需要 */}
            <h3 style = {{color:'red',fontSize:'33px',zIndex:3,fontWeight:200,textAlign:'center'}}>这是一个评论列表</h3>
            {this.state.TextList.map(item=><Getpinglun {...item} key={item.id}></Getpinglun>)}
        </div>
    }
}
但一般不会直接在组件虚拟Dom结构添加这样的内联样式,会做一定处理,比如在Getpinglun.jsx中
import React from 'react'

//jsx中写行内样式
// export default function Getpinglun(props){
//     {/* jsx语法中如果想加入行内样式那么应该照如下语法来写,属性值不是数字的要加引号,数字不需要 */}
//     return <div style={{border:'1px solid #ccc',margin:'10px',padding:'10px',boxShadow:'0 0 10px #ccc'}}>
//                 <h2 style={{fontSize:'16px'}}>评论人:{props.name}</h2>
//                 <p style = {{fontSize:'12px'}}>评论内容:{props.words}</p>
//             </div>
// }


//如果向上面这样写样式,反而会让结构看起来更加混乱因此可以将样式进行一下简单的封装处理 如下
// const divstyle = {border:'1px solid #ccc',margin:'10px',padding:'10px',boxShadow:'0 0 10px #ccc'};
// const hstyle = {fontSize:'16px'};
// const pstyle = {fontSize:'12px'};
// export default function Getpinglun(props){
//     return <div style={divstyle}>
//                 <h2 style={hstyle}>评论人:{props.name}</h2>
//                 <p style={pstyle}>评论内容:{props.words}</p>
//             </div>
// }


//可以进一步做如下处理
// const styles = {
//     divstyle:{border:'1px solid #ccc',margin:'10px',padding:'10px',boxShadow:'0 0 10px #ccc'},
//      hstyle:{fontSize:'16px'},
//      pstyle:{fontSize:'12px'}
// }
//  export default function Getpinglun(props){
//     return <div style={styles.divstyle}>
//                 <h2 style={styles.hstyle}>评论人:{props.name}</h2>
//                 <p style={styles.pstyle}>评论内容:{props.words}</p>
//             </div>
// }




//进一步将styles抽离为一个单独的样式模块 如下
//引入抽离出去的styles.js
import styles from '@/component/styles'
export default function Getpinglun(props){
    return <div style={styles.divstyle}>
                <h2 style={styles.hstyle}>评论人:{props.name}</h2>
                <p style={styles.pstyle}>评论内容:{props.words}</p>
            </div>
}

styles.js文件如下

export default {
    divstyle:{border:'1px solid #ccc',margin:'10px',padding:'10px',boxShadow:'0 0 10px #ccc'},
    hstyle:{fontSize:'16px'},
    pstyle:{fontSize:'12px'}
}

添加外联的样式表.css,启用css模块化,并且自定义生成类名的格式。比如,在组件Pingluntwo.jsx中引用pinglun.css

pinglun.css

.title{
    font-size:16px;
    text-align:center;
    font-weight:200;
}
/*css模块化处理只能够处理类选择器和id选择器,对于标签是无效的*/

/*通过local和global来设置类名是否被模块化,默认是所有类名和id都是local,可以被模块化,而设置了global后,则不会被模块化,会全局生效*/
:global(.test){
    color:yellow;
}

将以上css样式表导入到Pingluntwo.jsx组件中

import React from 'react'
//导入组件Getpinglun
import Getpingluntwo from './Getpingluntwo'
//如果在webpack.config.js中配置了路径 那么还可以使用别名来引入 如import Getpinglun from '@/component/Getpinglun'
//导入外部样式表
import classObj from '@/component/pinglun.css' 
//导入之后在运行之前需要安装来解析css的依赖并在webpack.config.js中配置好 npm install style-loader css-loader -D 否则无法解析css
//导入的该样式表会对该组件下包括嵌套组件都起作用,所以为了保证不会对其他结构产生影响,需要做进一步的处理 这里的处理方法往往就是对样式表进行模块化处理(同样在webpack.config.js进行处理)
//webpack.config.js中 {test: /\.css$/,use:['style-loader','css-loader?modules']}在css-loader后通过?添加参数modules,表示为普通的样式表,启用模块化。
//或者采用{test:/\.css$/,use:[{loader:'style-loader'},{loader:'css-loader',options:{modules:true}}]}的方式来为css开启模块化。
//还可以通过添加其他参数做如下配置{test:/\.css$/,use:['style-loader','css-loader?modules&localIdentName=[path][name]-[local]-[hash:5]']}或者{test:/\.css$/,use:[{loader:'style-loader'},{loader:'css-loader',options:{modules:{localIdentName:'[path][name]-[local]-[hash:base64:5]'}}}]}为模块化的css自定义生成类名的格式有效避免类名的重复。


//引入的外部框架的css在webpack.config.js文件中设置不对其进行模块化处理的方式如下

//有的时候需要在项目中使用其他的框架的ui,比如使用bootstrap.css,如果引用的外部其他框架的css进入项目,那么由于webpack.config.js中的配置
//也会对引入的css也进行模块化处理,模块化之后,在引用该css样式中的类时,通过访问属性的方式来为相关元素添加类名就显得及其繁琐,这时候,可以做其他处理 如下:
//将自己的样式表改为以scss或者less为结尾的文件,安装处理less或sass的依赖,并在webpack.config.js中配置好 npm install sass-loader node-sass  -D
//{test:/\.scss$/,use:[{loader:'style-loader'},{loader:'css-loader',options:{modules:{localIdentName:'[path][name]-[local]-[hash:base64:5]'}}},{loader:'sass-loader'}]}在css-loader后通过配置相关参数为普通的样式表启用模块化,并且自定义生成类名的格式
//并在webpack.config.js中将css配置为{test:/\.css$/,use:['style-loader','css-loader]} 这样就可以像正常一样使用外部引入的css了,如 <button class="btn btn-primary">按钮</button>而将自己定义的css后缀改为scss不会受到影响
//Sass是成熟、稳定、强大的CSS预处理器,而SCSS是Sass3版本当中引入的新语法特性,完全兼容CSS3的同时继承了Sass强大的动态功能。


//在引用某个包的时候,如果这个包安装到了node_modules中,在其他组件使用的时候,可以直接以包名引入自己的模块。


export default class Pinglun extends React.Component{
    constructor(){
        super()
        this.state = {
            TextList:[
                {id:0,name:'张三','words':'哈哈,沙发'},
                {id:0,name:'李四','words':'哈哈,板凳'},
                {id:0,name:'王五','words':'哈哈,凉席'},
                {id:0,name:'赵六','words':'哈哈,砖头'},
                {id:0,name:'田七','words':'哈哈,楼下山炮'}
            ]
        }
    }
    render(){
        return <div>
            {/*在需要的标签上,使用className指定模块化的样式*/}
            {/* <h3 className={classObj.title}>这是一个评论列表</h3> */}
            {/* <h3 className={classObj.title + ' test'}>这是一个评论列表</h3> 或者如下 */}
            <h3 className={[classObj.title, 'title'].join(' ')}>这是一个评论列表</h3>
            {this.state.TextList.map(item=><Getpinglun {...item} key={item.id}></Getpinglun>)}
        </div>
    }
}

getpinglun.css

.title{
    font-size: 14px;
}
.content{
    font-size:12px;
}
#box{
    border:1px solid #ccc;
    margin:10px;
    padding:10px;
    box-shadow:0 0 10px #ccc;
}

将getpinglun.css导入到Getpingluntwo.jsx中

import React from 'react'
import objtwo from '@/component/getpinglun.css'
export default function Getpinglun(props){
    return <div id={objtwo.box}>
                <h2 className={objtwo.title}>评论人:{props.name}</h2>
                <p className={objtwo.content}>评论内容:{props.words}</p>
            </div>
}
说明
  • webpack4.x中启用css模块化,配置方式如下:
{test: /\.css$/,use:['style-loader','css-loader?modules']}
或者{test:/\.css$/,use:[{loader:'style-loader'},{loader:'css-loader',options:{modules:true}}]}

此外,一般还会添加其他参数来自定义生成的类名的格式。
使用localIdentName自定义生成的类名格式,可选的参数有:
[path] 表示样式表 相对于项目根目录 所在路径
[name] 表示 样式表文件名称
[local] 表示样式的类名定义名称
[hash:length] 表示32位的hash值
所以为css样式表启用模块化并且来自定义生成的类名时一般在webpack.config.js中做如下配置:

 {test:/\.css$/,use:['style-loader','css-loader?modules&localIdentName=[path][name]-[local]-[hash:5]']}
//如果上面的配置无法使用还可以如下配置
{test:/\.css$/,use:[{loader:'style-loader'},{loader:'css-loader',options:{modules:{localIdentName:'[path][name]-[local]-[hash:base64:5]'}}}]}
  • 使用 :local() 和 :global()
    :local()包裹的类名,是被模块化的类名,只能通过className={cssObj.类名}来使用
    :local默认可以不写,这样,默认在样式表中定义的类名,都是被模块化的类名
    :global()包裹的类名,是全局生效的,不会被 css-modules 控制,定义的类名是什么,就是使用定义的类名className="类名"
    css模块化处理只能够处理类选择器和id选择器,对于标签是无效的

在项目中启用模块化并同时使用其他框架的样式表比如使用bootstrap的css

引入的外部框架的css在webpack.config.js文件中设置不对其进行模块化处理的方式如下

有的时候需要在项目中使用其他的框架的ui,比如使用bootstrap.css,如果引用的外部其他框架的css进入项目,那么由于webpack.config.js中的配置,也会对引入的css也进行模块化处理,模块化之后,在引用该css样式中的类时,通过访问属性的方式来为相关元素添加类名就显得及其繁琐,这时候,可以做其他处理 如下:

1、把 自己的样式表,定义为 .scss 文件。
2、第三方的 样式表,还是 以 .css 结尾。
3、我们只需要为自己的 .scss 文件,启用模块化即可。
4、运行npm install sass-loader node-sass -D 安装能够解析scss文件的loader。
5、webpack.config.js中添加loader规则如下:
{test:/\.scss$/,use:[{loader:'style-loader'},{loader:'css-loader',options:{modules:{localIdentName:'[path][name]-[local]-[hash:base64:5]'}}},{loader:'sass-loader'}]}, //添加打包处理 scss 文件的 loader

通过以上处理之后就会只对自己的css(添加了.scss)的样式表进行模块化处理,对于第三方的样式表不会模块化, 这样就可以像正常一样使用第三方的css了,如 <button class="btn btn-primary">按钮</button>,引用bootstrap的btn样式。

Sass是成熟、稳定、强大的CSS预处理器,而SCSS是Sass3版本当中引入的新语法特性,完全兼容CSS3的同时继承了Sass强大的动态功能。

13、React事件的绑定

  • 事件的名称是react提供的,事件名的首字母大写,并且遵循驼峰命名规范。
  • 为事件提供的处理函数,必须如下
onClick = {function}
react中常用的事件绑定方式如下
import React from 'react'

export default class BindEvent extends React.Component{
    constructor(){
        super()
        this.state = {}
    }
    render(){
        return <div>
                    我是BindEvent组件
                    <hr/>
                    {/* 在react中有一套自己的事件绑定机制,事件名小驼峰命名,参数必须为一个函数*/}
                    {/* <button onClick={function(){console.log('我是react当中的点击事件')}}>点击</button> */}
                    {/* <button onMouseOver={function(){console.log('我是react当中的onmouseover事件')}}>点击</button> */}
                    {/* <button onMouseMove={function(){console.log('我是react当中的onmousemove事件')}}>点击</button> */}
                    {/* 调用该实例对象下的clickEvent方法,点击才会执行 */}
                    {/* <button onClick={this.clickEvent}>点击</button> */}
                     {/* 调用该实例对象下的clickEvent方法,初始化时就开始执行一次 */}
                    {/* <button onClick={this.clickEvent()}>点击</button> */}
                    {/* react中绑定事件常用方式(箭头函数) 箭头函数内部,this指向外部环境的this,因此这里的this指向的是该实例 */}
                    <button onClick={()=>{this.clickEvent()}}>点击</button>

               </div>
    }
    // clickEvent(){
    //     console.log('哈哈哈哈哈')
    // }
    //将clickEvent改变为一个箭头函数
    clickEvent=()=>{
        console.log('哈哈哈哈')
    }
}
react中修改state中的数据,推荐使用this.setState({ }),如下
import React from 'react'

export default class BindEventTwo extends React.Component{
    constructor(){
        super();
        this.state = {
            message:'哈哈哈哈哈',
            name:'tom',
            age:20
        }
    }
    render(){
        return <div>
                <button onClick={()=>{this.changeState(1,2)}}>点击按钮修改state的message</button>
                <p>{this.state.message}</p>
        </div>
    }
    changeState=(n1,n2)=>{
        //这里的n1,n2是接收的调用该方法时的参数
        //在react,想要修改或者为state重新赋值,不能使用this.state.xxxx = 值的方式,应该调用react提供的this.setState({属性:值})的方式
        this.setState({
            message:'我被修改了'+n1+n2
        },function(){
            //修改状态值后的回调
            console.log(this.state.message)
        })
        //在React中推荐使用this.setState({})来修改状态值,并且在setState中,只会把对应的state状态更新,而不会覆盖其他的state状态
        //另外this.setState是一个异步的操作,所以在通过这种方式修改state后,如果希望第一时间得到修改过后的状态值,需要在this.setState({},callback)执行回调函数callback这里获取
    }

}
在React中实现类比Vue中的数据的双向绑定
  • Vue中提供了v-model指令来实现数据的双向绑定。
  • 在React中,默认是单向数据流,只能够把state中的数据绑定到页面,无法把页面中数据的变化自动同步到state。
  • 如果需要把页面上数据的变化,同步到state中,需要手动添加onChange事件,拿到最新的数据,并且调用this.setState( { } )将数据及时同步到state,以这种方式实现react的数据的双向流动。
  • Vue中为页面上的元素提供了ref属性,所以在Vue中可以使用this.$refs.xxx 来获取页面的元素引用。
  • React中也有ref,在React中通过this.refs.xxx来获取页面元素的引用。
    如下:
import React from 'react'

export default class BindEventThree extends React.Component{
    constructor(){
        super();
        this.state = {
            message:'哈哈哈哈哈',
            name:'tom'
        }
    }
    render(){
        return <div>
                <button onClick={()=>{this.changeState()}}>点击按钮修改state的message</button>
                <p>{this.state.message}</p>
                {/* react中当为input绑定value值后,必须要做的事情,要么为input 添加readOnly属性,要么为input添加onChange处理事件 */}
                {/* <input type="text" style={{width:'100%'}} value={this.state.message} readOnly /> */}
                <input type="text" style={{width:'100%'}} value={this.state.message} onChange={(e)=>{this.textChange(e)}}  ref='txt'/>
        </div>
    }

    //每当文本框的内容变化,自然会调用该事件
    textChange=(e)=>{
        //onChange事件中用于获取文本框的值有两种方案 
        //方案1、通过参数e来获取
        //console.log(e.target.value);
        //方案2、通过refs来获取
        //console.log(this.refs.txt.value)
        const newValues = e.target.value;
        this.setState({
            message:newValues
        })
    }
    changeState=()=>{
          this.setState({
                message:'啦啦啦啦啦啦'
          })
    }
}

14、React的生命周期

  • 先对Vue的生命周期有一个基本的了解


    Vue的生命周期图解
  • React的生命周期
    生命周期的概念:每个组件的实例,从 创建、到运行、直到销毁,在这个过程中,会出发一些列 事件,这些事件就叫做组件的生命周期函数;

React组件生命周期分为三部分:

  • 组件创建阶段:特点:一辈子只执行一次

componentWillMount: render: componentDidMount:

  • 组件运行阶段:按需,根据 props 属性 或 state 状态的改变,有选择性的 执行 0 到多次

componentWillReceiveProps: shouldComponentUpdate: componentWillUpdate: render: componentDidUpdate:

  • 组件销毁阶段:一辈子只执行一次
React生命周期图解

React生命周期各部分的详解

  • React各阶段生命周期回调函数概括总结如下表


    React各阶段生命周期回调函数概括总结
  • 组件生命周期的执行顺序
Mounting(组件创建阶段)

constructor();
componentWillMount();
render();
componentDidMount();

Updating(组件运行阶段)

componentWillReceiveProps(nextProps)
shouldComponentUpdate(nextProps,nextState)
componentWillUpdate((nextProps,nextState)
render()
componentDidUpdate(prevProps,prevState)

Unmounting(组件卸载或者销毁阶段)

componentWillUnmount();

关于defaultProps

创建组件之前,会先初始化默认的props属性,在全局调用一次,严格意义上来讲,这并不是组件生命周期的一部分。在组件创建并且加载的时候,首先调用constructor构造器中的this.state={},来初始化组件的状态。

defaultProps的使用
  • 在组件中通过static defaultProps来给组件的 props 提供默认值。
  • 设置默认属性值之后,如果外界没有传递相关参数,那么设置的该属性值在组件初始化的时候就会派上用场,当有相关参数传递的时候,那么外界传递的参数就会替代该设置的默认值。
propTypes的使用
  • 在组件中通过propTypes给组件的 props 进行类型校验。
  • 封装组件的目的是为了方便高效的开发,在封装组件的时候通常会为组件的一些必要数据进行校验,保证传递的参数是符合要求的,如果不符合要求,就在控制台给出警告。
  • React中通常会使用static propTypes对象对于外界传递的参数做类型校验,在React15的版本之前,prop-types并没有从React中抽离出来,在15的版本之后,才从React中抽离了出来,作为单独的一部分。
    也就是说,在React15的版本之前,不需要安装该模块,可以直接使用,但是在15版本之后,需要手动安装,才可以使用。 npm install prop-types 安装prop-types。

CounterTwo.jsx组件如下:

import React from 'react'
//导入参数传递校验的模块
import dataRules from 'prop-types'

export default class CounterTwo extends React.Component{
    constructor(props){
        super(props)
        this.state = {}
    }
    //在封装一些组件的时候,组件内部肯定有一些数据是必须的,哪怕用户并没有传递一些相关的启动参数,这个时候在组件的内部,尽量给自己提供一个默认值
    //在组件中通过static defaultProps来为组件设置默认属性值
    static defaultProps = {
        oneCount:10
        //设置默认属性值之后,如果外界没有传递相关参数,那么设置的该属性值在组件初始化的时候就会派上用场,当有相关参数传递的时候,那么外界传递的参数就会替代该设置的默认值。
    }
    

    //封装组件的目的是为了方便高效的开发,在封装组件的时候通常会为组件的一些必要数据进行校验,保证传递的参数是符合要求的,如果不符合要求,就在控制台给出警告。

    //React中通常会使用static propTypes对象对于外界传递的参数做类型校验,在React15的版本之前,prop-types并没有从React中抽离出来,在15的版本之后,才从React中抽离了出来,作为单独的一部分。
    //也就是说,在React15的版本之前,不需要安装该模块,可以直接使用,但是在15版本之后,需要手动安装,才可以使用。 npm install prop-types
    static propTypes = {
        //定义该组件传递的参数类型为number类型
        oneCount:dataRules.number
    }
    render(){
        return <div>
            <h3>这是一个Counter计数器组件</h3>
            <button>点击+1</button>
            <hr/>
            <h3>当前的数量是{this.props.oneCount}</h3>
        </div>
    }
}

index.js

// console.log("React真好用")
import React from 'react'
import ReactDom from 'react-dom'

import CounterTwo from '@/components/CounterTwo'


ReactDom.render(<div>
{/* 在这里规定每个使用该组件的用户必须传递一个默认的数量值,作为组件初始化的数据 */}
    <CounterTwo oneCount = {1}></CounterTwo>
    <hr/>
    <CounterTwo oneCount = '哈哈哈哈哈'></CounterTwo>
</div>,document.getElementById('app'))

Reac生命周期中组件创建阶段的过程详解

componentWillMount();

componentWillMount(): 组件将要挂载到页面时触发该函数,此时组件还没有挂载到页面上。而且内存中,虚拟的Dom结构还没有被创建出来,但是初始化时的默认属性和this.state的属性是可以在函数中访问到的,可以调用组件中定义的方法。类比Vue中生命周期的created()阶段。

注意点

ReactV16.3对生命周期的部分做了一些新的处理,为componentWillMount,componentWillReceiveProps,componentWillUpdate添加一个“UNSAFE_”前缀。 (这里,“不安全”不是指安全性,而是表示使用这些生命周期的代码将更有可能在未来的React版本中存在缺陷,特别是一旦启用了异步渲染)。
所以在16.3及以后的版本中用UNSAFE_componentWillMount来替代componentWillMount,不替换也可以但是在控制台会有警告。
详细内容:ReactV16.3即将更改的生命周期

render()

render():该阶段是在内存中创建虚拟Dom的阶段,整个过程结束后,内存中就已经创建完成了一个虚拟Dom,但是并未将其挂载到页面上,所以在这个阶段也是无法获取任何dom元素的。

componentDidMount()

组件已经挂载到了页面上后就进入了该阶段,在这个阶段中,页面已经存在可见的Dom元素了,在这个页面可以随心所欲的操作页面上的dom元素,在该阶段,在React中同样也可以用原生js来为元素绑定事件,尽管react中可以用原生js来为元素绑定事件,但是react中最好不要直接去操作dom元素,我们通常使用react提供的机制绑定事件。之后组件就进入了运行阶段。

React生命周期的创建阶段的各个回调函数的演示

组件CounterThree.jsx

import React from 'react'
//导入参数传递校验的模块
import dataRules from 'prop-types'

export default class CounterThree extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            message:'hello',
            //将props.oneCount赋值给state中的一个属性
            count:props.oneCount
        }
    }
    static defaultProps = {
        oneCount:10
    }
    static propTypes = {
        oneCount:dataRules.number
    }
    //componentWillMount 组件将要挂载到页面时触发该函数,此时组件还没有挂载到页面上
    //而且内存中,虚拟的Dom结构还没有被创建出来,但是初始化时的默认属性和this.state的属性是可以在函数中访问到的,可以调用组件中定义的方法
    UNSAFE_componentWillMount(){
        console.log(document.getElementById('oneDom'));//null 此时虚拟dom还未创建
        console.log(this.props.oneCount)//100
        console.log(this.state.message)//hello
        this.oneMethod();//我是oneMethod方法
    }
    render(){
        console.log(document.getElementById('oneDom'))
         console.log('-------------------')
        //在return之前 虚拟Dom还未创建完成,页面是空的,拿不到任何元素
        return <div>
            <h3 id = "oneDom">这是一个Counter计数器组件</h3>
            <button id="btn" onClick = {()=>this.changeData()}>点击+1</button>
            <hr/>
            <h3>当前的数量是{this.state.count}</h3>
        </div>
        //return 执行完毕之后,虚拟Dom结构已经创建完,但还没有挂载到页面上
    }
    componentDidMount(){
        //该阶段类比Vue中生命周期的mounted
        //如果我们想操作dom元素,最早可以在该阶段去操作
        console.log(document.getElementById('oneDom'))
      
        // document.getElementById('btn').onclick = ()=>{
        //     // console.log('原生js在react绑定的事件')
        //     this.setState({
        //         count:this.state.count+1
        //     })
        // }
    }
    oneMethod(){
        console.log('我是oneMethod方法')
    }
    changeData=()=>{
        this.setState({
            count:this.state.count+1
        })
    }
}

入口文件index.js

// console.log("React真好用")
import React from 'react'
import ReactDom from 'react-dom'

import CounterThree from '@/components/CounterThree'


ReactDom.render(<div>
{/* 在这里规定每个使用该组件的用户必须传递一个默认的数量值,作为组件初始化的数据 */}
    <CounterThree oneCount = {100}></CounterThree>
</div>,document.getElementById('app'))

Reac生命周期中组件运行阶段的过程详解

shouldComponentUpdate(nextProps,nextState);

shouldComponentUpdate()判断组件是否需要更新 返回布尔值;
返回true则会调用render()重新渲染页面,之后数据和页面都是最新的;
如果返回false,不会执行后续的生命周期函数,render()函数也不会调用,将会继续返回组件的运行中的状态,数据得到更新,组件的state状态会被修改,但是页面并没有重新渲染,是旧的。
在该组件中,通过this.state.count拿到的属性值是旧的,并不是最新的,在这里可以通过nextProps和nextState两个参数去获取到对应的最新的属性值。

componentWillUpdate()

组件将要更新阶段的状态,在该状态下,内存中的虚拟dom和页面上的dom还都是旧的,所以在该阶段要谨慎操作dom,因为很可能只是操作的旧的Dom。

render()

运行阶段再一次执行render()函数。在组件运行阶段,componentWillUpdate()过后还会再次调用render()函数,在render()执行完毕之前,页面上的dom还是旧的。

componentDidUpdate()

组件完成了更新的状态,在该状态下,数据和内存中的虚拟dom以及页面上的dom都是最新的,此时可以放心大胆的去操作Dom。

组件CounterFour.jsx

import React from 'react'
//导入参数传递校验的模块
import dataRules from 'prop-types'

export default class CounterThree extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            message:'hello',
            count:props.oneCount
        }
    }
    static defaultProps = {
        oneCount:10
    }
    static propTypes = {
        oneCount:dataRules.number
    }
    UNSAFE_componentWillMount(){
        console.log(document.getElementById('oneDom'));//null 此时虚拟dom还未创建
        console.log(this.props.oneCount)//100
        console.log(this.state.message)//hello
        this.oneMethod();//我是oneMethod方法
    }
    render(){
        //在组件运行阶段,componentWillUpdate()过后还会再次调用render()函数,在render()执行完毕之前,页面上的dom还是旧的。
        console.log(this.refs.h3 && this.refs.h3.innerHTML+'--------------运行阶段调用render()时')

        return <div>
            <h3 id = "oneDom">这是一个Counter计数器组件</h3>
            <button id="btn" onClick = {()=>this.changeData()}>点击+1</button>
            <hr/>
            <h3 ref = 'h3'>当前的数量是{this.state.count}</h3>
        </div>
    }
    componentDidMount(){
        console.log(document.getElementById('oneDom'))
    }
    shouldComponentUpdate(nextProps,nextState){
        //shouldComponentUpdate()判断组件是否需要更新 返回布尔值 返回true则会调用render()重新渲染页面,之后数据和页面都是最新的
        //如果返回false,不会执行后续的生命周期函数,render()函数也不会调用,将会继续返回组件的运行中的状态,数据得到更新,组件的state状态会被修改,但是页面并没有重新渲染,是旧的。
        //在该组件中,通过this.state.count拿到的属性值是旧的,并不是最新的,在这里可以通过nextProps和nextState去获取到对应的最新的属性值
        // return this.state.count%2?false:true
        return nextState.count%2?false:true
        //只有偶数时才更新页面
    }
    //组件将要更新阶段的状态,在该状态下,内存中的虚拟dom和页面上的dom还都是旧的,所以在该阶段要谨慎操作dom,因为很可能只是操作的旧的Dom
    componentWillUpdate(){
        console.log(this.refs.h3.innerHTML+'---------------componentWillUpdate');
        //打印出来的是旧dom的innerHTML
    }
    //组件完成了更新的状态,在该状态下,数据和内存中的虚拟dom以及页面上的dom都是最新的,此时可以放心大胆的去操作dom
    componentDidUpdate(){
        console.log(this.refs.h3.innerHTML+'---------------componentDidUpdate');
    }
    oneMethod(){
        console.log('我是oneMethod方法')
    }
    changeData=()=>{
        this.setState({
            count:this.state.count+1
        })
    }
}

入口文件 index.js

// console.log("React真好用")
import React from 'react'
import ReactDom from 'react-dom'

import CounterFour from '@/components/CounterFour'


ReactDom.render(<div>
{/* 在这里规定每个使用该组件的用户必须传递一个默认的数量值,作为组件初始化的数据 */}
    <CounterFour oneCount = {0}></CounterFour>
</div>,document.getElementById('app'))
运行阶段的componentWillReceiveProps()

该阶段,在组件将要接受外界传递过来的新的props属性值时触发,组件第一次被渲染到页面的时候是不会触发该状态的。只有通过某些事件,修改了props属性值之后,才会触发。

  • 注意:在React16.3的版本中,componentWillReceiveProps更名为UNSAFE_componentWillReceiveProps在UNSAFE_componentWillReceiveProps被触发的时候,如果通过this.props来获取属性值,获得的是修改之前的属性值。如果想要获得最新的属性值,要通过其参数列表来获取。UNSAFE_componentWillReceiveProps(nextProps)
    如下:
import React from 'react'

export default class CounterFive extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            message:'我是子组件'
        }
    }
    render(){
        return <div>
            <h3>我是父组件</h3>
            <button onClick = {()=>this.changeData()}>点击改变数据</button>
            <hr/>
            <Son msg = {this.state.message}></Son>
        </div>
    }
    changeData=()=>{
        this.setState({
            message:'哈哈哈,哈哈哈'
        })
    }
}
class Son extends React.Component{
    constructor(props){
        super(props)
        this.state = {}
    }
    render(){
        return <div>
               <h5>{this.props.msg}</h5>
        </div>
    }
    //第一次渲染时是不会触发该状态的,在传递的参数被修改后才会触发
    UNSAFE_componentWillReceiveProps(nextProps){
        //想要获得最新的属性值,要通过其参数列表来获取
        console.log(this.props.msg+'------'+nextProps.msg)
        //我是子组件 -------哈哈哈,哈哈哈
    }
}

15、React中绑定this传参的三种方式

import React from 'react'
import DataTypes from 'prop-types'

export default class CounterSix extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            message:'绑定this并传参的几种方式',
            datamsg:'我是数据'
        }
        //绑定this并传参的方式二:在构造函数中绑定并传参
        //当为一个函数绑定bind,改变this的指向后,bind函数调用的结果,有一个返回值,这个返回值是改变this指向的函数的引用
        this.changedata2 = this.changedata2.bind(this,1000,2000)
    }
    render(){
        return <div>
            <h3>{this.state.message}</h3>
            {/* bind的作用,为前面的函数,修改函数内部的this指向,让函数内部的this指向bind参数列表中的第一个参数。
            bind和call和apply之间的区别
            call和apply在修改完this的指向后会立即调用前面的函数
            但是bind不会立即调用。bind参数列表中的第一个参数是用来修改this指向的,之后的参数,会被当做将来调用前面的函数的参数传递进去。 */}
            <button onClick = {this.changedata.bind(this,100,200)}>绑定this并传参的方式一</button>
            <hr/>
            <button onClick = {this.changedata2}>绑定this并传参的方式二</button>
            <hr/>
            <button onClick = {()=>this.changedata3(200,500)}>绑定this并传参的方式三</button>
            <hr/>
            <p>{this.state.datamsg}</p>
        </div>
    }
    //值的注意的是,因为上面绑定处理方法的时候,使用了bind,所以这里可以不再使用箭头函数。
    changedata(num1,num2){
        this.setState({
            datamsg:'我被改变了'+num1+num2
        })
    }
    changedata2(num1,num2){
        this.setState({
            datamsg:'我被改变了'+num1+num2
        })
    }
    changedata3(num1,num2){
        this.setState({
            datamsg:'我被改变了'+num1+num2
        })
    }
}

16、一个评论列表的案例

  • 采用组件化的方式,创建一个评论列表 如下


    可以发表评论的评论列表

    index.js如下:

import React from 'react'
import ReactDom from 'react-dom'

import PList from '@/components/PList'

ReactDom.render(<div>
    <PList></PList>
</div>,document.getElementById('app'))

发表评论的组件 Sendpl.jsx

import React from 'react'

export default class Sendpl extends React.Component{
    constructor(props){
        super(props)
        this.state = {}
    }
    render(){
        return <div>
            <label htmlFor="pinglunren">评论人</label><br/>
            <input type="text" name = "pinglunren" ref = 'pinglunren'/><br/>
            <label htmlFor="contentBox">评论内容</label><br/>
            <textarea name="contentBox" id="contentBox" cols="30" rows="10" ref = "contentBox"></textarea>
            <button onClick = {()=>this.Addpl()}>发表评论</button>
        </div>
    }
    Addpl = ()=>{
        //1、获取评论人和评论内容
        //2、从本地存储中获取获取之前的评论数组
        //3、把最新的评论人和评论内容放到数组中
        //4、把最新的数组存储在本地并清空相关区域
        const content = {name:this.refs.pinglunren.value,words:this.refs.contentBox.value}
        const pllist = JSON.parse(localStorage.getItem('lists')||'[]')
        pllist.unshift(content)
        localStorage.setItem('lists',JSON.stringify(pllist))
        console.log(localStorage.getItem('lists'))
        this.refs.pinglunren.value=this.refs.contentBox.value=''
        //调用传递的getPL方法刷新评论列表
        this.props.reloadlist()

    }
}

评论列表组件Plitem.jsx组件

import React from 'react'

export default class Plitem extends React.Component{
    constructor(props){
        super(props)
        this.state = {}
    }
    render(){
        return <div style = {{border:'1px solid #ccc',margin:'15px 0'}}>
            <h3>评论人:{this.props.name}</h3>
            <p>评论内容:{this.props.words}</p>
        </div>
    }
}

组件间的嵌套关系 Plist.jsx

import React from 'react'
import Plitem from '@/components/Plitem'
import Sendpl from '@/components/Sendpl'

export default class PList extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            list:[
                {id:0,name:'tom',words:'hello'},
                {id:1,name:'jack',words:'world'},
                {id:2,name:'cat',words:'byebye'}
            ]
        }
    }
    render(){
        return <div>
            {/* 评论标题 */}
            <h3>评论列表</h3>

            {/* 发表评论组件 */}
            {/* 在react中传递给组件数据或者方法都可以使用this.props.属性(或者方法名)来调用 ,这与Vue中数据传递使用props,方法传递使用this.$emit(方法名)是有不同的*/}
            {/* 在该组件点击发表评论时应该再一次调用UNSAFE_componentWillMount中执行的方法getPL(),刷新评论列表 */}
            <Sendpl reloadlist = {this.getPL}></Sendpl>


            {/* 评论列表组件 */}
            {this.state.list.map(item=>{
                return <Plitem {...item} key={item.name}></Plitem>
            })}
        </div>
    }
    //获取评论数组
    getPL=()=>{
        var getpl = JSON.parse(localStorage.getItem('lists')||'[]')
        this.setState({
            list:getpl
        })
    }
    //虚拟Dom挂载到页面之前调用getPL该方法,从本地取出数据替换掉之前的假数据
    UNSAFE_componentWillMount(){
        this.getPL()
    }
}

效果如下

react创建一个可以添加的评论列表

17、React中的context特性

  • 当一个组件内的其他子组件需要使用父组件的数据时,为了避免不必要的繁琐,可以使用context特性
    如下:
import React from 'react'
import ReactTypes from 'prop-types'

// export default class Father extends React.Component{
//     constructor(props){
//         super(props)
//         this.state = {
//             color:'red'
//         }
//     }
//     render(){
//         return <div>
//             <h2>我是父组件</h2>
//             <Son color = {this.state.color}></Son>
//         </div>
//     }
// }
// class Son extends React.Component{
//     constructor(props){
//         super(props)
//         this.state = {}
//     }
//     render(){
//         return <div>
//             <h4>我是子组件</h4>
//             <Grandson color = {this.props.color}></Grandson>
//         </div>
//     }
// }
// class Grandson extends React.Component{
//     constructor(props){
//         super(props)
//         this.state = {}
//     }
//     render(){
//         return <div style = {{color:this.props.color}}>
//             <h5>我是孙子组件</h5>
//         </div>
//     }
// }
//以上案例的目的,是孙组件如果想用到父组件的state里的值,
//是经过了多次传导才得到的,而且子组件并没有使用该值,但是也参与了其中,这样子看上去过于繁琐,所以为了避免有时候出现这种状况,可以使用react中的Context的属性,如下:。


export default class Father extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            color:'red'
        }
    }
    // react中Context的使用
    //1、在父组件中,创建一个function,它有个固定的名称,getChildContext,这个方法内部返回一个对象,将需要共享给其他子组件的数据包含在其中。
    //2、需要使用属性校验,规定共享给子组件的数据的类型,childContextTypes
    getChildContext(){
        return {
            color:this.state.color
        }
    }
    static childContextTypes = {
        color:ReactTypes.string
    }
    render(){
        return <div>
            <h2>我是父组件</h2>
            <Son></Son>
        </div>
    }
}
class Son extends React.Component{
    constructor(props){
        super(props)
        this.state = {}
    }
    render(){
        return <div>
            <h4>我是子组件</h4>
            <Grandson></Grandson>
        </div>
    }
}
class Grandson extends React.Component{
    constructor(props){
        super(props)
        this.state = {}
    }
    //使用父组件共享的数据时同样也是必须先进行校验
    static contextTypes = {
        color:ReactTypes.string
    }
    render(){
        return <div>
            <h5 style = {{color:this.context.color}}>我是孙子组件----{this.context.color}</h5>
        </div>
    }

}

18、React中的路由

React中路由的基本使用
  • 在react中使用路由首先需要安装react的路由模块 npm install react-router-dom -S。
  • 在项目主入口文件index.js或根组件处导入。
import {HashRouter,Route,Link} from 'react-router-dom'

HashRouter:表示路由的根容器,将来所有跟路由相关的内容都需要包裹在HashRouter里面,一个网站中只需要一个HashRouter就好。
Route 路由规则,有两个重要的属性 path和component。
Link表示路由的连接,相当于Vue中的<router-link-to=""></router-link>
使用实例

index.js

import React from 'react'
import ReactDom from 'react-dom'
import App from '@/components/App'


ReactDom.render(<div>
    <App></App>
</div>,document.getElementById('app'))

根组件

import React from 'react'
import Home from '@/components/Home'
import Movie from '@/components/Movie'
import About from '@/components/About'

//在react中使用路由需要安装路由模块 npm install react-router-dom -S,之后需要在主入口将其导入
import {HashRouter,Route,Link} from 'react-router-dom'
//HashRouter表示路由的根容器,将来所有跟路由相关的内容都需要包裹在HashRouter里面,一个网站中只需要一个HashRouter就好
//Route 路由规则,有两个重要的属性 path和component
//Link表示路由的连接,相当于Vue中的<router-link-to=""></router-link>

export default class App extends React.Component{
    constructor(props){
        super(props)
        this.state = {}
    }
    render(){
        //用HashRouter来包裹根组件, 为当前网站启用路由,在HashRouter中只能有唯一一个根元素,如此处的div
        return <HashRouter>
            <div>
                <h3>我是网站App的根组件</h3>
                <Link exact to = "/">首页</Link>&nbsp;&nbsp;
                <Link to = "/movie">电影</Link>&nbsp;&nbsp;
                <Link to = "/about">我的</Link>&nbsp;&nbsp;
                <hr/>
                {/* 添加路由规则,path表示匹配的路由,component表示需要展示的组件*/}
                {/* 在Vue中有<router-view></router-view>这样的路由标签,专门用来放匹配到的路由组件,在react中,没有router-view,而是直接使用Route标签来当做占位符。 */}
                {/* react中的Route有两种身份,首先是路由规则,其次还是占位符 */}
                {/* 这里的exact是精确匹配的意思,比如我们有多层路由进行嵌套时,exact可以帮助我们精确匹配到你想跳转的路由。exact的值为bool型,为true是表示严格匹配,为false时为正常匹配 */}
                <Route exact path = "/" component = {Home}></Route>
                <Route path = "/movie" component = {Movie}></Route>
                <Route path = "/about" component = {About}></Route>
            </div>
        </HashRouter>
    }
}

其他组件
以Home组件为例

import React from 'react'

export default class Home extends React.Component{
    constructor(props){
        super(props)
        this.state = {}
    }
    render(){
        return <div>
            <h4>我是首页</h4>
        </div>
    }
}
路由传参,匹配路由参数

在路由规则中向movie组件中传参如下

import React from 'react'
import Home from '@/components/Home'
import Movie from '@/components/Movie'
import About from '@/components/About'

//在react中使用路由需要安装路由模块 npm install react-router-dom -S,之后需要在主入口将其导入
import {HashRouter,Route,Link} from 'react-router-dom'
//HashRouter表示路由的根容器,将来所有跟路由相关的内容都需要包裹在HashRouter里面,一个网站中只需要一个HashRouter就好
//Route 路由规则,有两个重要的属性 path和component
//Link表示路由的连接,相当于Vue中的<router-link-to=""></router-link>

export default class App extends React.Component{
    constructor(props){
        super(props)
        this.state = {}
    }
    render(){
        //用HashRouter来包裹根组件, 为当前网站启用路由,在HashRouter中只能有唯一一个根元素,如此处的div
        return <HashRouter>
            <div>
                <h3>我是网站App的根组件</h3>
                <Link to = "/">首页</Link>&nbsp;&nbsp;
                <Link to = "/movie/top100/5">电影</Link>&nbsp;&nbsp;
                <Link to = "/about">我的</Link>&nbsp;&nbsp;
                <hr/>
                
                {/* 添加路由规则,path表示匹配的路由,component表示需要展示的组件*/}
                {/* 在Vue中有<router-view></router-view>这样的路由标签,专门用来放匹配到的路由组件,在react中,没有router-view,而是直接使用Route标签来当做占位符。 */}
                {/* react中的Route有两种身份,首先是路由规则,其次还是占位符 */}
                {/* 默认情况下,react中的路由是模糊匹配的,如果路由可以部分匹配成功,就会展示这个路由对应的组件 */}
                {/* 这里的exact是精确匹配的意思,比如我们有多层路由进行嵌套时,exact可以帮助我们精确匹配到你想跳转的路由。exact的值为bool型,为true是表示严格匹配,为false时为正常匹配 */}
                {/* 在路由中想要传递参数可以在匹配规则中使用:修饰符,表示这个位置匹配到的是参数 */}
                {/* 这里添加的replace的作用去除警告  Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack */}
                <Route exact path = "/" component = {Home}></Route>
                <Route exact path = "/movie/:type/:id" component = {Movie} replace></Route>
                <Route path = "/about" component = {About}></Route>
            </div>
        </HashRouter>
    }
}

Movie组件中获取路由中传递的参数

import React from 'react'

export default class Movie extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            routeParams:props.match.params
        }
    }
    render(){
        // 如果想要在路由规则中提取匹配的参数进行使用可以使用this.props.match.params来访问
        console.log(this.props.match.params)
        return <div>
            <h4>我是电影 参数为{this.state.routeParams.type}---{this.state.routeParams.id} </h4>
        </div>
    }
}
React中antd的使用
  • antd是什么
    antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。
  • 为React项目启用Ant Design组件
    1、安装 npm install antd -S
    2、在主入口文件引入antd的样式表 import 'antd/dist/antd.css
    3、在想使用antd组件的react组件中将antd组件导入
    4、将导入的antd组件以标签的方式丢到使用的位置即可
  • antd组件的按需导入
    如果在引用第三方框架的时候,将整个css全部引入进来,虽然是可以的,但是打包之后的文件体积显得过大
    有时候我们只引用了某个组件,那么这时再将整个的样式表全部引入就显得不是那么合适,所以可以通过配置将所需的样式按需自动加载,同时也避免了在主入口文件引入样式表。
    实现按需导入的方式
    1、安装用于按需加载组件代码和样式的 babel 插件。npm install babel-plugin-import
    2、在.babellrc中的plugins中添加如下配置 ["import", { "libraryName": "antd", "style": "css" }]或者在babel-loader中配置
    在.babellrc中配置如下
{
"presets":["env","stage-0","react"],
"plugins":["transform-runtime",["import", { "libraryName": "antd", "style": "css" }]]
}
React中使用antd组件,并实现按需加载的实例

index.js

import React from 'react'
import ReactDom from 'react-dom'
import Appone from '@/components/Appone'
//导入Ant-Design的样式表
// import 'antd/dist/antd.css'
//如果在引用第三方框架的时候,像上面这样将整个css全部引入进来,虽然是可以的,但是打包之后的文件体积显得过大
//有时候我们只引用了某个组件或者ui,那么这时再将整个的样式表全部引入就显得不是那么合适,所以可以通过配置将所需的样式按需自动加载
//同时也避免了在主入口文件引入样式表

//实现按需加载的方式
//1、安装用于按需加载组件代码和样式的 babel 插件npm install babel-plugin-import
//2、在.babellrc中的plugins中添加如下配置 ["import", { "libraryName": "antd", "style": "css" }]


ReactDom.render(<div>
    <Appone></Appone>
</div>,document.getElementById('app'))

Appone.jsx

import React from 'react'
import locale from 'antd/es/date-picker/locale/zh_CN'
import {DatePicker} from 'antd'

export default class Appone extends React.Component{
    constructor(props){
        super(props)
        this.state = {}
    }
    render(){
        return <div>
            <h3>使用ant-design</h3>
            <DatePicker locale = {locale}></DatePicker>
        </div>
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容