React的基本学习(三)——class和列表评论案例

前言:

承接前面的内容,继续来学习React

 

 

 

 

 

 


Class

上一次的内容我们学到 通过定义一个构造函数来创建组件,现在我们来通过另一种方式创建组件

什么是class

我们先来了解一下什么是ES6的class

我们来备份一下上次写的index.js,写入新内容来测试class

import React from 'react'
import ReactDOM from 'react-dom'

import '@/class基本使用.js'

ReactDOM.render(<div>
    123
</div>,document.getElementById('app'))

我们新建一个文件,同目录下叫 class基本使用.js,由于这个文件没有暴露模块所以这里直接导入路径即可,我们就是要在这个文件中测试class。

我们普通的function来创建对象

function Person(name,age){
    this.name=name
    this.age=age
}
const p1=new Person('王多多',18)
console.log(p1)

而使用类的话,如下:

//创建了一个动物类
class Animal{
    //类中的constryctor构造器
    //每一个类中都会有一个构造器
    //若没有指定构造器,系统会有个隐形的空构造器
    //构造器的作用:每当new这个类的时候,必然会优先执行构造器中的代码
    constructor(name,age){
        this.name=name
        this.age=age
    }
}

const a1=new  Animal('大黄',3)
console.log(a1)

我们放在一起:

function Person(name,age){
    this.name=name
    this.age=age
}
const p1=new Person('王多多',18)
console.log(p1)

console.log('----------------------------------')

//创建了一个动物类
class Animal{
    //类中的constryctor构造器
    //每一个类中都会有一个构造器
    //若没有指定构造器,系统会有个隐形的空构造器
    //构造器的作用:每当new这个类的时候,必然会优先执行构造器中的代码
    constructor(name,age){
        this.name=name
        this.age=age
    }
}

const a1=new  Animal('大黄',3)
console.log(a1)

其中,我们的 p1 和 a1 叫做实例,他们的name、age叫做实例属性

 

static创建静态属性

如果我们用类来分配属性,这样的行为就是给构造函数挂载了属性,这就是静态属性,实例是不能调用静态属性的。

class Animal{
    //类中的constryctor构造器
    //每一个类中都会有一个构造器
    //若没有指定构造器,系统会有个隐形的空构造器
    //构造器的作用:每当new这个类的时候,必然会优先执行构造器中的代码
    constructor(name,age){
        this.name=name
        this.age=age
    }
}

Animal.info='aaaa'

const a1=new  Animal('大黄',3)
console.log(a1)

console.log(a1.info)

如果这样写,那么最后的输出就会输出 undefined,把 a1 换成 Animal 即可。

在我们的class中,可以直接这样来定义静态属性:

class Animal{
    constructor(name,age){
        this.name=name
        this.age=age
    }
    //在class内部,通过static修饰的属性,就是静态属性
    static info="eee"
}

const a1=new  Animal('大黄',3)
console.log(a1)

实例方法和静态方法

实例方法:

使用构造函数来创建对象的话怎么加入 实例方法?

利用prototype(原型对象)即可

function Person(name,age){
    this.name=name
    this.age=age
}
Person.prototype.say=function (){
    console.log("这是person的实例方法")
}

const p1=new Person('王多多',18)
Person.info="bbbbb"
console.log(p1)
console.log(Person.info)
p1.say()

那么对于class的对象呢?

直接在class内定义即可

//创建了一个动物类
class Animal{
    //类中的constryctor构造器
    //每一个类中都会有一个构造器
    //若没有指定构造器,系统会有个隐形的空构造器
    //构造器的作用:每当new这个类的时候,必然会优先执行构造器中的代码
    constructor(name,age){
        this.name=name
        this.age=age
    }
    //在class内部,通过static修饰的属性,就是静态属性
    static info="eee"

    //动物的实例方法
    jiao(){
        console.log('动物的实例方法')
    }
}

const a1=new  Animal('大黄',3)
a1.jiao()  //调用实例方法
console.log(a1)

在class中直接定义和在原型对象上定义的效果是一样的。

 

静态方法:

一样的道理,挂载给构造函数的就是静态方法

function Person(name,age){
    this.name=name
    this.age=age
}
Person.prototype.say=function (){
    console.log("这是person的实例方法")
}
Person.show=function (){
    console.log("静态方法")
}
Person.show()

const p1=new Person('王多多',18)
Person.info="bbbbb"
console.log(p1)
console.log(Person.info)
p1.say()

我们的静态方法挂载给了构造函数

对于class也是一样的,加上关键字static即可

class Animal{
    constructor(name,age){
        this.name=name
        this.age=age
    }
    //在class内部,通过static修饰的属性,就是静态属性
    static info="eee"

    //动物的实例方法
    jiao(){
        console.log('动物的实例方法')
    }
    static show(){
        console.log("有意思")
    }
}

const a1=new  Animal('大黄',3)
a1.jiao()
console.log(a1)
Animal.show()

Class的注意点

  • class内部,必须要有构造器
  • 在class的()区域内,只能写构造器、静态方法和静态属性、实例方法
  • class关键字内部,还是用原来的配方实现的(构造函数),所以我们把class关键字叫做 语法糖

 

 


Class的继承

换一个文件

import React from 'react'
import ReactDOM from 'react-dom'

//import '@/class基本使用.js'
import '@/class继承.js'

ReactDOM.render(<div>
    123
</div>,document.getElementById('app'))

我们在 class继承.js 中实现继承

//父类
class Person{
    constructor(name,age){
        this.name=name;
        this.age=age;
    }
}

class American extends Person{

}

const a1=new American('Jack',30)
console.log(a1)

class Chinese extends Person{

}

const c1=new Chinese("小明",22)
console.log(c1)

这样,构造时会调用父类的构造函数

使用共有的方法也是一样的

//父类
class Person{
    constructor(name,age){
        this.name=name;
        this.age=age;
    }
    sayHello(str){
        console.log(str)
    }
}

class American extends Person{

}

const a1=new American('Jack',30)
a1.sayHello("我是美国人")
console.log(a1)

class Chinese extends Person{

}

const c1=new Chinese("小明",22)
c1.sayHello("我是中国人")
console.log(c1)

而如果我们在子类中再调用构造器,那一定要注意:

  • 一定要在构造器中手动调用 super,而且要优先调用
  • super是一个函数,即父类的构造器的一个引用

所以我们完善一下代码,就是如下:

//父类
class Person{
    constructor(name,age){
        this.name=name;
        this.age=age;
    }
    sayHello(str){
        console.log(str)
    }
}

class American extends Person{
    constructor(name,age){
        super(name,age)
    }
}

const a1=new American('Jack',30)
a1.sayHello("我是美国人")
console.log(a1)

class Chinese extends Person{
    //姓名,年龄,身份证(中国人独有)
    constructor(name,age,IDnum){
        super(name,age)
        this.IDnum=IDnum;
    }
}

const c1=new Chinese("小明",22,14041)
c1.sayHello("我是中国人")
console.log(c1)

 

 


Class创建组件

渲染出来

用class创建组件非常方便,下面是模板

class 组件名称 extends React.Component{
       render(){
             return jsx代码
       }
}

即可。

示例 index.js

import React from 'react'
import ReactDOM from 'react-dom'

//import '@/class基本使用.js'
//import '@/class继承.js'

class Movie extends React.Component{
    render(){
        return <h1>有意思!</h1>
    }
}

ReactDOM.render(<div>
    123
     {/*这里的Movie是实例标签,相当于创造了一个实例对象*/}
    <Movie></Movie>
</div>,document.getElementById('app'))

 

传递参数

用class封装的组件接收参数很有意思,不需要像函数那样设置形参来接收。

直接使用 this.props.XXX 即可接收

如下:

import React, { useReducer } from 'react'
import ReactDOM from 'react-dom'

//import '@/class基本使用.js'
//import '@/class继承.js'

class Movie extends React.Component{
    render(){
    return <h1>
        有意思!--
        {this.props.name}--
        {this.props.age}
    </h1>
    }
}

const user={
    name:'小红',
    age:22
}

ReactDOM.render(<div>
    123
    <Movie {...user}></Movie>
</div>,document.getElementById('app'))

和function创建组件一样,class创建组件传来的参数props也是只读的,不可被赋值

class创建组件方法 和 function创建组件方法 的对比:

  • 使用class关键字创建的组件,有自己的私有数据(this.state)和生命周期函数
  • 使用function创建的组件,只有props,没有私有数据和声明周期函数

 

this.state

关于state,更多细节可以参考这个菜鸟教程

import React, { useReducer } from 'react'
import ReactDOM from 'react-dom'

//import '@/class基本使用.js'
//import '@/class继承.js'

class Movie extends React.Component{
    //构造器
    constructor(){
        //由于Movie组件,继承了React.Component父类,
        //所以,自定义的构造器中,必须调用super()
        super()
        //只有调用了super()以后,才能使用 this 关键字
        this.state={   
            msg:'大家好,我是class创建的Movie组件'
        }
        
    }


    //render函数的作用,是渲染当前组件所对应的虚拟DOM元素
    render(){
    return <h1>
        有意思!--
        {this.props.name}--
        {this.props.age}
        <div>{this.state.msg}</div>
    </h1>
    }
}

const user={
    name:'小红',
    age:22
}

ReactDOM.render(<div>
    123
    <Movie {...user} />
</div>,document.getElementById('app'))

这个state是一个可读又可写的属性

根据这个state:

  • 用构造函数创建出来的组件:叫做“无状态组件”
  • 用class关键字创建出来的组件:叫做“有状态组件”
  • 有状态组件和无状态组件之间的本质区别就是:有无 state 属性 和 有无生命周期函数

 

 


评论列表案例

基础实现显示

import React from 'react' 
import ReactDOM from 'react-dom'

//使用class 关键字定义父组件
class CmtList extends React.Component{
    constructor(){
        super()
        this.state={
            CommentList:[     //评论列表数据
                {id:1,user:'小明',constent:'有意思?'},
                {id:2,user:'小红',constent:'真可爱'},
                {id:3,user:'小王',constent:'卢本伟牛逼'},
                {id:4,user:'小刚',constent:'好厉害'},
                {id:5,user:'小敏',constent:'我是赌神!'},
            ]
        }
    }
    render(){
        return <div>
                <h1>这是评论列表组件</h1>
        {this.state.CommentList.map(item=><div key={item.id}>
        <h1>用户: {item.user}</h1>
        <p>内容:{item.constent}</p>
                </div>)}
        </div>
    }
}

ReactDOM.render(<div>
     <CmtList />
</div>,document.getElementById('app'))

CommentList 的值是一个对象数组,我们可以使用数组的函数 map 来遍历每一个对象的键

优化一下,我们使用无状态组件(构造函数)配合展开运算符

import React from 'react' 
import ReactDOM from 'react-dom'
//使用 function 构造函数,定义普通的无状态组件
function CmtItem(props){
    return <div >
    <h1>用户: {props.user}</h1>
    <p>内容:{props.constent}</p>
    </div>
}

//使用class 关键字定义父组件
class CmtList extends React.Component{
    constructor(){
        super()
        this.state={
            CommentList:[     //评论列表数据
                {id:1,user:'小明',constent:'有意思?'},
                {id:2,user:'小红',constent:'真可爱'},
                {id:3,user:'小王',constent:'卢本伟牛逼'},
                {id:4,user:'小刚',constent:'好厉害'},
                {id:5,user:'小敏',constent:'我是赌神!'},
            ]
        }
    } 
    render(){
        return <div>
                <h1>这是评论列表组件</h1>
        {/*每一个item是一个对象,我们使用...展开运算符可以直接将item传入*/}
        {this.state.CommentList.map(item=><CmtItem {...item} key={item.id}/>)}
        </div>
    }
}

ReactDOM.render(<div>
     <CmtList />
</div>,document.getElementById('app'))

效果是一样的,我们想在将它放在多个文件中

#/src/components/CmtItem.jsx
import React from "react"

//使用 function 构造函数,定义普通的无状态组件
export default function CmtItem(props){
    return <div >
    <h1>用户: {props.user}</h1>
    <p>内容:{props.constent}</p>
    </div>
}


#/src/components/CmtList.jsx
import React from 'react'

//导入评论项 子组件
import CmtItem from '@/components/Cmtitem.jsx'

//使用class 关键字定义父组件
export default class CmtList extends React.Component{
    constructor(){
        super()
        this.state={
            CommentList:[     //评论列表数据
                {id:1,user:'小明',constent:'有意思?'},
                {id:2,user:'小红',constent:'真可爱'},
                {id:3,user:'小王',constent:'卢本伟牛逼'},
                {id:4,user:'小刚',constent:'好厉害'},
                {id:5,user:'小敏',constent:'我是赌神!'},
            ]
        }
    } 
    render(){
        return <div>
                <h1>这是评论列表组件</h1>
        {this.state.CommentList.map(item=><CmtItem {...item} key={item.id}/>)}
        </div>
    }
}


#src/index.js
import React from 'react' 
import ReactDOM from 'react-dom'

//导入评论项 子组件
import CmtList from '@/components/CmtList.jsx'

ReactDOM.render(<div>
     <CmtList />
</div>,document.getElementById('app'))

分成了三个文件,这样就好管理多了

美化页面

#Cmtitem.jsx
import React from "react"

//第一层封装
// const itemStyle={
//     border:'2px dashed #cda',
//     margin:'30px',
//     padding:'30px',
//     boxShadow:'0 0 10px red'
// }
// const userStyle={
//     fontSize:'20px',
//     textAlign:'center'
// }
// const contentStyle={
//     fontSize:'24px',
//     textAlign:'center'
// }

//第二层封装
const styles={
    item:{
        border:'2px dashed #cda',     
        margin:'30px',
        padding:'30px',
        boxShadow:'0 0 10px red'
    },
    user:{
       fontSize:'20px',
       textAlign:'center'
    },
    content:{
        fontSize:'24px',
        textAlign:'center'
    }
}

//使用 function 构造函数,定义普通的无状态组件
export default function CmtItem(props){
             {/*CSS盒模型-边框设置: 像素-样式-颜色 */}
    return <div style={styles.item}>
    <h1 style={styles.user}>用户: {props.user}</h1>
    <p style={styles.content}>内容:{props.constent}</p>
    </div>
}

也可以另外开一个新文件

#styles.js
export default{
        item:{
            border:'2px dashed #cda',     
            margin:'30px',
            padding:'30px',
            boxShadow:'0 0 10px red'
        },
        user:{
           fontSize:'20px',
           textAlign:'center'
        },
        content:{
            fontSize:'24px',
            textAlign:'center'
        }
}




#Cmtitem.jsx
import React from "react"
import styles from '@/components/styles'

//使用 function 构造函数,定义普通的无状态组件
export default function CmtItem(props){
             {/*CSS盒模型-边框设置: 像素-样式-颜色 */}
    return <div style={styles.item}>
    <h1 style={styles.user}>用户: {props.user}</h1>
    <p style={styles.content}>内容:{props.constent}</p>
    </div>
}

是不是比原来强多了

 

使用CSS样式美化组件

我们来使用css样式美化我们的组件,将上面的美化都先删掉

#CmtList.jsx
import React from 'react'

//导入评论项 子组件
import CmtItem from '@/components/Cmtitem.jsx'

//使用class 关键字定义父组件
export default class CmtList extends React.Component{
    constructor(){
        super()
        this.state={
            CommentList:[     //评论列表数据
                {id:1,user:'小明',constent:'有意思?'},
                {id:2,user:'小红',constent:'真可爱'},
                {id:3,user:'小王',constent:'卢本伟牛逼'},
                {id:4,user:'小刚',constent:'好厉害'},
                {id:5,user:'小敏',constent:'我是赌神!'},
            ]
        }
    } 
    render(){
        return <div>
            <h1 >这是评论列表组件</h1>
        {this.state.CommentList.map(item=><CmtItem {...item} key={item.id}/>)}
        </div>
    }
}
#Cmtitem.jsx
import React from "react"

//使用 function 构造函数,定义普通的无状态组件
export default function CmtItem(props){
    return <div>
    <h1 >用户: {props.user}</h1>
    <p >内容:{props.constent}</p>
    </div>
}

React默认是不会解析css文件的,我们先安装两个第三方插件:

npm i style-loader css-loader -D

安装完成后,在webpack.config.js中进行配置

const path=require('path')
//导入 “在内存中自动生成index页面” 的插件
const HtmlWebPackPlugin=require('html-webpack-plugin')

//创建一个插件的实例对象
const htmlPlugin=new HtmlWebPackPlugin({
    //源文件
    template:path.join(__dirname,"./src/index.html"),
    //生成的内存中首页的名称
    filename:'index.html'
})



//下面有node的语法,因为webpack是基于node构建的,支持node API和语法
/*webpack默认只能打包处理.js后缀名类型的文件;像 .png、.vue 无法主动处理,
所以要配置第三方的loader*/
//向外暴露一个打包的配置对象
module.exports={
    mode:'development',  
    //在webpack 4.x中,有一个很大的特性,就是 约定大于配置
    //默认打包入口路径是 src/index.js
    plugins:[
        htmlPlugin
    ],
    module:{   //所有第三方模块的配置规则
        rules:[  //第三方匹配规则
            //千万别忘记exclude排除项
            {test: /\.js|jsx$/,use:'babel-loader',exclude:/node_modules/},
            //css-loader的规则
            //打包处理css样式表的第三方(先后顺序不可变)
            {test: /\.css$/,use:['style-loader','css-loader']}
        ]
    },
    //固定写法
    resolve:{
        //表示这几个文件的文件名可以不写了
        extensions:['.js','.jsx','json'],
        //使@符号表示项目根目录中的src这一层路径
        alias:{
            '@':path.join(__dirname,'./src')
        }
    }
}

好,我们创建一个css目录,建立一个Cmtlist.css,写入内容:

.title{
    color:red;
}

好,我们先试着修改一个标题,修改CmtList.jsx文件

import React from 'react'

//导入css
import cssobj from '@/css/Cmtlist.css'

//导入评论项 子组件
import CmtItem from '@/components/Cmtitem.jsx'

//使用class 关键字定义父组件
export default class CmtList extends React.Component{
    constructor(){
        super()
        this.state={
            CommentList:[     //评论列表数据
                {id:1,user:'小明',constent:'有意思?'},
                {id:2,user:'小红',constent:'真可爱'},
                {id:3,user:'小王',constent:'卢本伟牛逼'},
                {id:4,user:'小刚',constent:'好厉害'},
                {id:5,user:'小敏',constent:'我是赌神!'},
            ]
        }
    } 
    render(){
        return <div>
            <h1 className='title'>这是评论列表组件</h1>
        {this.state.CommentList.map(item=><CmtItem {...item} key={item.id}/>)}
        </div>
    }
}

然后标题就变红了!

 

cssobj是一个空对象,因为Cmtlist.css样式表并没有暴露

另外,注意:直接导入的css样式表,默认是在全局上(整个项目)都生效,只要你在任何组件中用到了样式表,那么都将生效。

比如我在CmtList.css中加入这么一个性质

h1{
    font-style: italic;
}

那么,整个页面的h1标签内的内容都会是斜体。

 

为普通样式表通过modules参数启用模块化

上面我们说了css样式表默认是对全局有效,那么我们要怎么使其部分有效呢?我们可以修改webpack.config.js配置文件的内容:

//下面有node的语法,因为webpack是基于node构建的,支持node API和语法
/*webpack默认只能打包处理.js后缀名类型的文件;像 .png、.vue 无法主动处理,
所以要配置第三方的loader*/
//向外暴露一个打包的配置对象
module.exports={
    mode:'development',  
    //在webpack 4.x中,有一个很大的特性,就是 约定大于配置
    //默认打包入口路径是 src/index.js
    plugins:[
        htmlPlugin
    ],
    module:{   //所有第三方模块的配置规则
        rules:[  //第三方匹配规则
            //千万别忘记exclude排除项
            {test: /\.js|jsx$/,use:'babel-loader',exclude:/node_modules/},
            //css-loader的规则
            //打包处理css样式表的第三方(先后顺序不可变)
            //?modules:通过问号给css-loader加参数,表示为普通css样式表启用模块化
            {test: /\.css$/,use:['style-loader','css-loader?modules']}
             
        ]
    },
    //固定写法
    resolve:{
        //表示这几个文件的文件名可以不写了
        extensions:['.js','.jsx','json'],
        //使@符号表示项目根目录中的src这一层路径
        alias:{
            '@':path.join(__dirname,'./src')
        }
    }
}

之后,在CmtList.js中导入的cssobj可就不是一个空对象了。

我们就可以直接调用cssobj的某一属性了

#CmtList.jsx
import React from 'react'

//导入css
import cssobj from '@/css/Cmtlist.css'

//导入评论项 子组件
import CmtItem from '@/components/Cmtitem.jsx'

//使用class 关键字定义父组件
export default class CmtList extends React.Component{
    constructor(){
        super()
        this.state={
            CommentList:[     //评论列表数据
                {id:1,user:'小明',constent:'有意思?'},
                {id:2,user:'小红',constent:'真可爱'},
                {id:3,user:'小王',constent:'卢本伟牛逼'},
                {id:4,user:'小刚',constent:'好厉害'},
                {id:5,user:'小敏',constent:'我是赌神!'},
            ]
        }
    } 
    render(){
        return <div>
            <h1 className={cssobj.title}>这是评论列表组件</h1>
        {this.state.CommentList.map(item=><CmtItem {...item} key={item.id}/>)}
        </div>
    }
}

效果还是和上面一样只有标题是红的,但是封装性得到很大改善。

但是跟着我做实验的小伙伴可能要问了,为什么上面定义的

h1{
    font-style: italic;
}

还是生效(即h1标签下的内容还是斜体)

这是因为:

  • css模块化,只针对 类选择器 和 ID选择器
  • css模块化,不会将 标签选择器 模块化

下面我们通过模块化修改特殊id

#Cmtlist.css
/*class选择符前面加前缀符号‘.’*/
.title{
    color:red;
}
h1{
    font-style: italic;
}
/*id选择符前面应该加前缀符号‘#’*/
#cmtTitle{
    font-size: 14px;
}
#CmtItem.jsx
import React from "react"

import cssobj from '@/css/Cmtlist.css'

//使用 function 构造函数,定义普通的无状态组件
export default function CmtItem(props){
    return <div>
    <h1 id={cssobj.cmtTitle}>用户: {props.user}</h1>
    <p >内容:{props.constent}</p>
    </div>
}

懂了上面的道理,我们就可以来美化一下了

#Cmtlist.css
/*class选择符前面加前缀符号‘.’*/
.title{
    color:red;
    text-align: center;
    font-weight: 200;
}
/*id选择符前面应该加前缀符号‘#’*/
#cmtTitle{
    font-size: 14px;
}





#CmtItem.css
.title{
    font-size: 14px;
}
.content{
    font-size: 12px;
}
.cmtbox{
    /*border:边界*/
    border: 1px dashed #ccc;
    /*页面空白*/
    margin:20px;
    /*衬垫*/
    padding: 10px;
    box-shadow: 0 0 10px red;
}




#CmtItem.cs
import React from "react"

import cssobj from '@/css/CmtItem.css'

//使用 function 构造函数,定义普通的无状态组件
export default function CmtItem(props){
    return <div className={cssobj.cmtbox}>
    <h1 className={cssobj.title}>用户: {props.user}</h1>
    <p className={cssobj.content}>内容:{props.constent}</p>
    </div>
}

好看一些了哈。

使用 localIdentName 来自定义模块化的类名

自动生成的类型非常不好看

我们可以设置自定义模块化的类名

使用 localIdentName 自定义生成类名格式,可选参数:

  • [path]:表示样式表相对于根目录所在路径
  • [name]:表示样式表文件名称
  • [local]:表示样式的类名定义名称
  • [hash:length]:表示32位的hash值

修改配置文件webpack.config.js 的module:如下

    module:{   //所有第三方模块的配置规则
        rules:[  //第三方匹配规则
            //千万别忘记exclude排除项
            {test: /\.js|jsx$/,use:'babel-loader',exclude:/node_modules/},
            //css-loader的规则
            //打包处理css样式表的第三方(先后顺序不可变)
            //?modules:通过问号给css-loader加参数,表示为普通css样式表启用模块化
            { test: /\.css$/,use: ['style-loader',
                       { loader: 'css-loader',
                         options: {
                             modules: {
                                  localIdentName: '[path][name]-[local]'},
                                }
                            }
                        ]
                     }
             
        ]
    },

使用localIdentName自定义生成格式可选的参数有:

  1. path:表示样式表相对于项目根目录所在路径;
  2. name:表示样式表文件的名称;
  3. local:表示样式的类名定义名称;
  4. hash:length:表示32位的hash值。
  5. 在webpack.config.js文件中modules后面添加进去

 

 


 

 

 

 

 

 

发表评论