前言:
承接前面的内容,继续来学习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自定义生成格式可选的参数有:
- path:表示样式表相对于项目根目录所在路径;
- name:表示样式表文件的名称;
- local:表示样式的类名定义名称;
- hash:length:表示32位的hash值。
- 在webpack.config.js文件中modules后面添加进去