前言:
承接前面的內容,繼續來學習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後面添加進去