文章目录
- 一、React 组件
- 二、React 组件通信 - 父子通信
- 三、React 组件通信 - 子父通信
- 四、React 组件通信 - 兄弟通信
- 五、React 组件通信 - 跨组件通信(祖先)
- 六、结合组件通信案例
- 七、props-children 属性
- 八、props-类型校验
- 九、React 生命周期
- 十、setState 扩展
一、React 组件
1. 函数组件:使用JS函数创建的组件
注:函数名称首字母必须大写;函数必须要有返回值,如果没有可以返回null
import React from "react";
import ReactDom from "react-dom";
function Header() {
return <div>头部</div>
}
const Footer = () => {
return <div>底部</div>
}
const Loading = () => {
const loading = false
return loading ? <div>加载中...</div> : null
}
const Div = () => {
return (
<>
<Header/>
<Loading/>
<Footer/>
</>
)
}
ReactDom.render(<Div />,document.getElementById('root'))
也可拆分成多个组件引入
Header.js
import React from 'react';
export default function Header() {
return (
<div>
头部
</div>
);
};
Footer.js
import React from 'react';
const Footer = () => {
return (
<div>
底部
</div>
);
};
export default Footer;
Loading.js
import React from 'react';
const Loading = () => {
const loading = true
return loading ? <div>加载中...</div> : null
};
export default Loading;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Header from "./components/Header";
import Footer from "./components/Footer";
import Loading from "./components/loading";
const App = () => {
return (
<div>
<Header />
<Loading />
<Footer />
</div>
);
};
ReactDOM.render(<App/>,document.getElementById('root'))
2. 类组件:使用class语法创建的组件
ES6 类继承:class 创建类,extends 继承类,可以使用父类的属性和函数
注:类名首字母大写;必须继承 React.Component 父类;必须有 render 函数,无渲染可返回null
App.js
import React from 'react';
import Header from "./components/Header";
import Loading from "./components/Loading";
import Footer from "./components/Footer";
// 创建类组件
class App extends React.Component {
render() {
return (
<>
<Header />
<Loading />
<Footer />
</>
)
}
}
// 暴露
export default App;
Header.jsx
import React, {Component} from 'react';
export default class Header extends Component {
render() {
return (
<div>
Header
</div>
);
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js'
ReactDOM.render(<App/>,document.getElementById('root'))
3. 组件区分
1)无状态组件
- 组件本身不定义状态,没有生命周期,只负责UI渲染
- React16.8 之前的函数组件都是无状态组件,Hooks 出现后函数组件也可以有状态
2)有状态组件
- 组件本身有独立数据,拥有组件的生命周期,存在交互行为
- class 组件可以定义组件自己的状态,拥有组件的生命周期,是有状态组件
3)区别
- 无状态组件没有维护状态只做渲染,性能较好;有状态组件提供数据和生命周期,能力更强。
- React16.8 之前,组件不需要维护数据只渲染就使用 函数组件 ,有数据和交互使用 类组件你需要去判断。
- React16.8 之后,Hooks 出现给函数提供状态,建议使用函数组件。
4. 定义状态
// 定义 state属性 定义状态 它的值是对象
// 使用 state的时候通过this去访问即可
// 数据发生变化,驱动视图更新
import React, {Component} from 'react';
export default class App extends Component {
// vue 数据定义在 data中
// react 类组件数据定义在 成员属性state中
state = {
title:'数码产品',
list:['电脑','手机','平板']
}
// render函数是模板渲染函数 组件被使用他会自动调用
// render() {
// return (
// <div>
// <p>{this.state.title}</p>
// <ul>
// {this.state.list.map( (item,index) => <li key={index}>{item}</li>)}
// </ul>
// </div>
// );
// }
render() {
// 解构数据
const { title, list} = this.state;
return (
<div>
<p>{title}</p>
<ul>
{list.map( (item,index) => <li key={index}>{item}</li>)}
</ul>
</div>
);
}
}
5. 绑定事件
- 在类中声明事件处理函数,在标签上使用 on+事件名称={处理函数} 的方式绑定事件,事件名称需要遵循 大驼峰 规则。
- 处理函数默认的参数就是事件对象,可以使用事件对象处理默认行为和事件冒泡。
import React, {Component} from 'react';
const styleDes = {
color:'red',
fontSize:'20px'
}
export default class App extends Component {
state = {
count:20
}
handleMouseEnter = () => {
console.log('鼠标移入')
}
handleClick = (event) => {
console.log(event,'event')
// 阻止默认行为
event.preventDefault()
}
render() {
const { count } = this.state;
return (
<div>
<div style={ styleDes } onMouseEnter={this.handleMouseEnter}>计数器:{count}</div>
<hr/>
<a href='https://www.baidu.com' onClick={this.handleClick}>按钮</a>
</div>
);
}
}
6. this指向问题并解决
- 在事件处理函数中打印 this.state.count 发现报错,this 是个 undefined。(指向的是window)
- 演示函数调用对 this 指向的影响,得出函数谁调 this 就执行谁。
- 找出原因: 处理函数不是通过组件去调用的,导致出现 this 不是组件问题。
- 解决方案:
- 1. 模板中使用箭头函数
- 2. 定义箭头函数
- 3. 模板中使用 bind 绑定
import React, { Component } from 'react'
const styleDesc = {
color:'red',
fontSize:'20px'
}
export default class App extends Component {
state = {
count: 10
}
handleClick(event){
event.preventDefault() // 阻止默认行为
console.log(this); // 打印this undefined 空!
}
// 方式2: 【定义箭头函数】 留存this的指向
handleClick2 = (event)=>{
event.preventDefault() // 阻止默认行为
console.log(this); // 打印this undefined 空!
}
handleMouseEnter(){
console.log("鼠标移入了");
}
render() {
return (
<div>
{/* 语法:on事件名={事件函数} */}
<p style={styleDesc} onMouseEnter={this.handleMouseEnter} >
计数器:{ this.state.count }
</p>
<div>
{/* 方式1:【模板里箭头函数】 真实的事件函数是箭头函数,箭头函数好处是留住this的指向 */}
<a href="https://www.baidu.com" onClick={ (e)=> this.handleClick(e) }>按钮1</a>
===
<a href="https://www.baidu.com" onClick={this.handleClick2}>按钮2</a>
===
{/* 方式3:【模板里面bind绑定】使用bind方法将函数的this指向给定义好,bind返回新函数,给onClick */}
<a href="https://www.baidu.com" onClick={ this.handleClick.bind(this) }>按钮3</a>
</div>
</div>
)
}
}
7. 事件传参并且获取事件对象
- 使用特点:
- 1. 不传递参数,使用方法3
- 2. 传递实参,又要获取事件对象,使用方法2
import React, { Component } from 'react'
export default class App extends Component {
state = {
count: 10
}
// 最后1位形参就是事件对象
handleClick1(num,event){
console.log(num);
event.preventDefault()
console.log(this);
}
handleClick2(e,num){
e.preventDefault()
console.log(num);
console.log(this);
}
// 3. 定义事件函数为箭头函数
handleClick3 = ()=>{
console.log(this);
}
render() {
return (
<div>
{/* 1. 模板里面bind */}
<a href='https://www.baidu.com' onClick={ this.handleClick1.bind(this,10) }>按钮1</a>
====
{/* 2. 模板里箭头函数, 箭头函数才是真正的事件函数,只不过执行了别的代码 */}
<a href='https://www.baidu.com' onClick={ (event)=>this.handleClick2(event,20) }>按钮2</a>
====
{/* 不能传参! */}
<button onClick={ this.handleClick3 }>按钮3,传30</button>
</div>
)
}
}
8. 类组件 - setstate 使用
在react里 类组件里修改state数据需要 setState(修改对象)。setState函数调用之后render函数执行,模板也就更新,展示出最新的数据
import React, { Component } from 'react'
export default class App extends Component {
state = {
count: 10,
user:{
name:'Tom',
age:20
},
list:['电脑','手机']
}
handleClick(){
this.setState({
count: this.state.count + 1
})
}
changeArr = () => {
this.setState({
list:[...this.state.list,'平板']
})
}
changeAge(num){
this.setState({
user:{
...this.state.user,
age: this.state.user.age + num
}
})
}
render() {
return (
<div>
<div>计数器:{this.state.count}</div>
<br/>
<button onClick={ () => this.handleClick() }>count+1</button>
<br/>
<p>{this.state.list.join('-')}
<button onClick={ this.changeArr }>新增</button>
</p>
<p>
姓名:{this.state.user.name}
<br/>
年龄:{this.state.user.age}
<button onClick={ () => this.changeAge(1) }>增加</button>
</p>
</div>
)
}
}
9. 类组件 - 受控组件
表单元素的值被 React 中 state 控制,这个表单元素就是受控组件。
如何绑定表单元素,如 input:text input:checkbox
import React, { Component } from 'react'
export default class App extends Component {
state = {
mobile:'13312344556',
isChange:true
}
render() {
return (
<div>
<p>手机号:<input type="tel" value={this.state.mobile}></input></p>
<p><input type="checkbox" checked={this.state.isChange} id="i"></input>同意</p>
</div>
)
}
}
提示需要加上onChange事件
import React, { Component } from 'react'
export default class App extends Component {
state = {
mobile:'13312344556',
isChange:true
}
changeMobile = (event)=>{
this.setState({
mobile:event.target.value
})
}
changeAgree = (event)=>{
this.setState({
isChange:event.target.checked
})
}
render() {
return (
<div>
{/* state里面的数据 控制输入框的初始值 一定要用onChange事件修改state里面的数据 */}
<p>手机号:<input type="tel" value={this.state.mobile} onChange={ this.changeMobile }></input></p>
<p><input type="checkbox" checked={this.state.isChange} id="i" onChange={ this.changeAgree }></input>同意</p>
</div>
)
}
}
10. 类组件 - 非受控组件
没有通过 state 控制的表单元素,它自己控制自身的值,就是非受控组件。
通过 ref 获取表单元素获取非受控组件的值。
// 1. 通过createRef 创建一个ref对象
// 2. 给元素绑定 ref属性值为创建的ref对象
// 3. 通过ref对象的current获取元素 再获取值
import React, {Component, createRef} from 'react'
export default class App extends Component {
// 1
mobilRef = createRef()
agreeRef = createRef()
submitButton = () => {
// 3
console.log(this.mobilRef.current.value)
console.log(this.agreeRef.current.checked)
}
render() {
return (
<div>
// 2
{/* 输入数据完全不受控制 最终通过获取DOM或组件实例来读取对应的数据内容 */}
<p>手机号:<input type="tel" ref={ this.mobilRef }></input></p>
<p><input type="checkbox" id="i" ref={ this. agreeRef }></input>同意</p>
<br/>
<button onClick={ this.submitButton }>提交</button>
</div>
)
}
}
案例
public/index.html
<link href="https://at.alicdn.com/t/font_2998849_vtlo0vj7ryi.css" rel="stylesheet"/>
App.js
import { Component } from 'react';
import Comment from './components/Comment.jsx';
class App extends Component {
render() {
return (
<>
<Comment />
</>
);
}
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js'
ReactDOM.render(<App/>,document.getElementById('root'))
src/components/index.css
body {
margin: 0;
}
.comments {
background-color: #121212;
color: #eee;
padding: 24px;
width: 1000px;
margin: 0 auto;
}
.comm-head {
color: #eee;
font-size: 24px;
line-height: 24px;
margin-bottom: 24px;
}
.comm-head sub {
font-size: 14px;
color: #666;
margin-left: 6px;
bottom: 0.2em;
position: relative;
}
.comm-head span {
display: inline-block;
line-height: 1;
padding: 5px 16px;
font-size: 14px;
font-weight: normal;
border-radius: 12px;
background-color: rgba(255, 255, 255, 0.1);
color: #999;
cursor: pointer;
margin-left: 30px;
}
.comm-head span:hover,
.comm-head span.active {
color: #61f6ff;
}
.comm-list {
list-style: none;
padding: 0;
}
.comm-item {
display: flex;
margin-bottom: 24px;
}
.comm-item .avatar {
width: 48px;
height: 48px;
line-height: 48px;
border-radius: 24px;
display: inline-block;
cursor: pointer;
background-position: 50%;
background-size: 100%;
background-color: #eee;
}
.comm-item .info {
padding-left: 16px;
}
.comm-item .info p {
margin: 8px 0;
}
.comm-item .info p.name {
color: #999;
}
.comm-item .info p.vip {
color: #ebba73;
}
.comm-item .info p.vip img {
width: 14px;
vertical-align: baseline;
margin-left: 5px;
}
.comm-item .info p.time {
color: #666;
font-size: 14px;
display: flex;
align-items: center;
}
.comm-item .info .iconfont {
margin-left: 20px;
position: relative;
top: 1px;
cursor: pointer;
}
.comm-item .info .iconfont.icon-collect-sel {
color: #ff008c;
}
.comm-item .info .del {
margin-left: 20px;
cursor: pointer;
}
.comm-item .info .del:hover {
color: #ccc;
}
.comm-input {
border-radius: 6px;
padding: 18px;
background-color: #25252b;
}
.comm-input textarea {
border: 0;
outline: 0;
resize: none;
background: transparent;
color: #999;
width: 100%;
font-family: inherit;
height: auto;
overflow: auto;
}
.comm-input .foot {
display: flex;
justify-content: flex-end;
justify-items: center;
}
.comm-input .foot .word {
line-height: 36px;
margin-right: 10px;
color: #999;
}
.comm-input .foot .btn {
background-color: #ff008c;
font-size: 14px;
color: #fff;
line-height: 36px;
text-align: center;
border-radius: 18px;
padding: 0 24px;
cursor: pointer;
user-select: none;
}
src/components/Comment.js
import React, { Component } from 'react';
import './index.css';
export default class Comment extends Component {
state = {
// 当前用户
user: {
name: '清风徐来',
vip: true,
avatar: 'https://static.youku.com/lvip/img/avatar/310/6.png',
},
// 评论列表
comments: [
{
id: 102,
name: '__RichMan',
avatar: 'https://r1.ykimg.com/051000005BB36AF28B6EE4050F0E3BA6',
content:
'这阵容我喜欢😍靳东&闫妮,就这俩名字,我就知道是良心剧集...锁了🔒',
time: '2021/10/12 10:10:23',
vip: true,
collect: false,
},
{
id: 101,
name: '糖蜜甜筒颖',
avatar:
'https://image.9xsecndns.cn/image/uicon/712b2bbec5b58d6066aff202c9402abc3370674052733b.jpg',
content:
'突围神仙阵容 人民的名义第三部来了 靳东陈晓闫妮秦岚等众多优秀演员实力派 守护人民的财产 再现国家企业发展历程',
time: '2021/09/23 15:12:44',
vip: false,
collect: true,
},
{
id: 100,
name: '清风徐来',
avatar: 'https://static.youku.com/lvip/img/avatar/310/6.png',
content:
'第一集看的有点费力,投入不了,闫妮不太适合啊,职场的人哪有那么多表情,一点职场的感觉都没有',
time: '2021/07/01 00:30:51',
vip: true,
collect: false,
},
],
// 评论内容
content:''
}
contentChange = (event) => {
// 输入的最新值做判断是否大于100
if(event.target.value.length > 100) return;
this.setState({
content:event.target.value
})
}
addComment = () => {
const len = this.state.comments.length
const newData = {
...this.state.user,
content: this.state.content,
collect: false,
id:len?this.state.comments[0].id*1+1:1,
time: (new Date()).toLocaleString()
}
this.setState({
comments:[newData,...this.state.comments],
content:''
})
}
delectComment(id) {
if(!window.confirm('确定要删除吗?')) return;
this.setState({
comments:this.state.comments.filter(item => item.id !== id)
})
}
Collect(id){
const newArr = this.state.comments.map(item => {
if(item.id == id) item.collect = !item.collect
return item
})
this.setState({
comments: newArr
})
}
render() {
const { comments,content,user } = this.state
return (
<div className="comments">
{/* 输入框区域 */}
<h3 className="comm-head">评论</h3>
<div className="comm-input">
<textarea placeholder="爱发评论的人,运气都很棒" value={ content } onChange={ this.contentChange }></textarea>
<div className="foot">
<div className="word">{ content.length }/100</div>
<div className="btn" onClick={ this.addComment }>发表评论</div>
</div>
</div>
{/* 头部区域 */}
<h3 className="comm-head">
热门评论<sub>({comments.length})</sub>
</h3>
{/* 评论列表区域 */}
<ul className="comm-list">
{ comments.map(item => {
return (
<li className="comm-item" key={item.id}>
<div className="avatar" style={{ backgroundImage: `url(${item.avatar})` }}></div>
<div className="info">
<p className="name vip">
{item.name}
{ item.vip ? <img src="https://gw.alicdn.com/tfs/TB1c5JFbGSs3KVjSZPiXXcsiVXa-48-48.png" /> : ''}
</p>
<p className="time">
{ item.time }
{/*<span className={ item.collect ? 'iconfont icon-collect-sel' : 'iconfont icon-collect'} ></span>*/}
<span className={ `iconfont icon-collect${item.collect ? '-sel' : ''}`} onClick={ ()=>this.Collect(item.id) }></span>
{ item.name == user.name ? <span className="del" onClick={ this.delectComment.bind(this,item.id)}>删除</span> : ''}
</p>
<p>
{ item.content }
</p>
</div>
</li>
)
})}
</ul>
</div>
);
}
}
二、React 组件通信 - 父子通信
- 使用组件的时候通过属性绑定数据,在组件内部通过 props 获取即可。
- 单向数据流:父组件传递数据给子组件,父组件更新数据子组件自动接收更新后数据,子组件是不能修改数据的。
- 可以传递任意数据(字符串,数字,布尔,数组,对象,函数,JSX(插槽))
- 如果传递的数据是数组里面的每项值的话 可以不用一个一个写 直接 {...数组名称}
三、React 组件通信 - 子父通信
- 父组件通过props将修改数据的方法,传递给子组件,让子组件调用
- 父组件传递给子组件的方法需要用箭头函数,不让this指向变化
四、React 组件通信 - 兄弟通信
- 状态提升:将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态和修改状态的方法
- 需要通讯的组件通过 props 接收状态和函数即可
五、React 组件通信 - 跨组件通信(祖先)
- 一个范围,只要在这个范围内,就可以跨级组件通讯。(不需要 props 层层传递)
- 使用 context 完成跨级组件通讯。
六、结合组件通信案例
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js'
import './index.css'
ReactDOM.render(<App/>,document.getElementById('root'))
index.css
body {
margin: 0;
}
.comments {
background-color: #121212;
color: #eee;
padding: 24px;
width: 1000px;
margin: 0 auto;
}
.comm-head {
color: #eee;
font-size: 24px;
line-height: 24px;
margin-bottom: 24px;
}
.comm-head sub {
font-size: 14px;
color: #666;
margin-left: 6px;
bottom: 0.2em;
position: relative;
}
.comm-head span {
display: inline-block;
line-height: 1;
padding: 5px 16px;
font-size: 14px;
font-weight: normal;
border-radius: 12px;
background-color: rgba(255,255,255,0.1);
color: #999;
cursor: pointer;
margin-left: 30px;
}
.comm-head span:hover , .comm-head span.active{
color: #61f6ff;
}
.comm-list {
list-style: none;
padding: 0;
}
.comm-item {
display: flex;
margin-bottom: 24px;
}
.comm-item .avatar {
width: 48px;
height: 48px;
line-height: 48px;
border-radius: 24px;
display: inline-block;
cursor: pointer;
background-position: 50%;
background-size: 100%;
background-color: #eee;
}
.comm-item .info {
padding-left: 16px;
}
.comm-item .info p {
margin: 8px 0;
}
.comm-item .info p.name {
color: #999;
}
.comm-item .info p.vip {
color: #ebba73;
}
.comm-item .info p.vip img {
width: 14px;
vertical-align: baseline;
margin-left: 5px;
}
.comm-item .info p.time {
color: #666;
font-size: 14px;
display: flex;
align-items: center;
}
.comm-item .info .iconfont {
margin-left: 20px;
position: relative;
top: 1px;
cursor: pointer;
}
.comm-item .info .iconfont.icon-collect-sel {
color: #ff008c;
}
.comm-item .info .del {
margin-left: 20px;
cursor: pointer;
}
.comm-item .info .del:hover {
color: #ccc;
}
.comm-input {
border-radius: 6px;
padding: 18px;
background-color: #25252b;
}
.comm-input textarea {
border: 0;
outline: 0;
resize: none;
background: transparent;
color: #999;
width: 100%;
font-family: inherit;
height: auto;
overflow: auto;
}
.comm-input .foot {
display: flex;
justify-content: flex-end;
justify-items: center;
}
.comm-input .foot .word {
line-height: 36px;
margin-right: 10px;
color: #999;
}
.comm-input .foot .btn {
background-color: #ff008c;
font-size: 14px;
color: #fff;
line-height: 36px;
text-align: center;
border-radius: 18px;
padding: 0 24px;
cursor: pointer;
user-select: none;
}
app.js
import React, {Component} from 'react';
import CommentHeader from "./components/CommentHeader";
import CommentInput from "./components/CommentInput";
import CommentList from "./components/CommentList";
export default class App extends Component {
state = {
user: {
name: '清风徐来',
vip: true,
avatar: 'https://static.youku.com/lvip/img/avatar/310/6.png',
},
// 评论列表
comments: [
{
id: 100,
name: '__RichMan',
avatar: 'https://r1.ykimg.com/051000005BB36AF28B6EE4050F0E3BA6',
content:
'这阵容我喜欢😍靳东&闫妮,就这俩名字,我就知道是良心剧集...锁了🔒',
time: '2021/10/12 10:10:23',
vip: true,
collect: false,
},
{
id: 101,
name: '糖蜜甜筒颖',
avatar:
'https://image.9xsecndns.cn/image/uicon/712b2bbec5b58d6066aff202c9402abc3370674052733b.jpg',
content:
'突围神仙阵容 人民的名义第三部来了 靳东陈晓闫妮秦岚等众多优秀演员实力派 守护人民的财产 再现国家企业发展历程',
time: '2021/09/23 15:12:44',
vip: false,
collect: true,
},
{
id: 102,
name: '清风徐来',
avatar: 'https://static.youku.com/lvip/img/avatar/310/6.png',
content:
'第一集看的有点费力,投入不了,闫妮不太适合啊,职场的人哪有那么多表情,一点职场的感觉都没有',
time: '2021/07/01 00:30:51',
vip: true,
collect: false,
},
],
active:'default' // default 默认id time时间
}
changeActive = (str) => {
this.setState({
active:str
})
}
addComment = (comment) => {
let newComment = {
...this.state.user,
collect: false,
time: new Date().toLocaleString(),
id:Math.ceil(Math.random()*100),
content:comment
}
this.setState({
comments:[...this.state.comments,newComment]
})
}
// 点赞
delCollect = (id) => {
let newComments = this.state.comments.map(item => {
if(item.id === id) {
item.collect = !item.collect
}
return item
})
this.setState({
comments:newComments
})
}
// 删除
delComment = (id) => {
if(!window.confirm('确定要删除吗?')) return;
let newArr = this.state.comments.filter(item => item.id !== id)
this.setState({
comments:newArr
})
}
render() {
const { user, comments,active } = this.state
return (
<div className="comments">
<CommentInput addComment={ this.addComment } />
<CommentHeader len={ comments.length } changeActive={ this.changeActive } active={active} />
<CommentList delComment={ this.delComment } delCollect={ this.delCollect } comments={ comments } active={active} user={user}/>
</div>
);
}
}
CommentInput.js
import React, {Component} from 'react';
export default class CommentInput extends Component {
state = {
content:''
}
changeInput = (e) => {
if(e.target.value.length > 100) return
// if(e.target.value.length === 0) return alert('请输入内容');
this.setState({
content:e.target.value
})
}
submitContent = () => {
if(this.state.content.length === 0) return alert('请输入内容');
this.props.addComment(this.state.content)
this.setState({
content:''
})
}
render() {
const { content } = this.state
return (
<>
{/* 输入框区域 */}
<h3 className="comm-head">评论</h3>
<div className="comm-input">
<textarea value={ content } placeholder="爱发评论的人,运气都很棒" onChange={ this.changeInput }></textarea>
<div className="foot">
<div className="word">{content.length}/100</div>
<div className="btn" onClick={ this.submitContent }>发表评论</div>
</div>
</div>
</>
);
}
}
CommentHeader.js
import React, {Component} from 'react';
export default class CommentHeader extends Component {
render() {
const { len,active,changeActive } = this.props
return (
<>
{/* 头部区域 */}
<h3 className="comm-head">
热门评论<sub>({len})</sub>
<span onClick={ () => changeActive('default')} className={ active === 'default' ? 'active' : 'default' } > 默认</span>
<span onClick={ () => changeActive('time')} className={ active === 'time' ? 'active' : 'default' } > 时间</span>
</h3>
</>
);
}
}
CommentList.js
import React, {Component} from 'react';
export default class CommentList extends Component {
render() {
const { comments,active,user,delCollect,delComment } = this.props
// 处理时间 使用time2为了方便后面展示
comments.map(item => {
item.time2 = new Date(item.time).getTime()
})
const newList = [...comments]
// 按照id排序
if(active === 'default') {
newList.sort((a,b) => b.id-a.id)
}
// 按照time排序
if(active === 'time') {
newList.sort((a,b) => b.time2-a.time2)
}
return (
<>
{/* 评论列表区域 */}
<ul className="comm-list">
{
newList.map(item=>{
return (
<li className="comm-item" key={item.id}>
<div className="avatar" style={ {backgroundImage:`url(${item.avatar})`}}></div>
<div className="info">
<p className="name vip">
{item.name}
{item.vip ? <img src="https://gw.alicdn.com/tfs/TB1c5JFbGSs3KVjSZPiXXcsiVXa-48-48.png" /> : ''}
</p>
<p className="time">
{item.time}
<span className={`iconfont icon-collect${item.collect?'-sel':''}`} onClick={()=>delCollect(item.id)}></span>
{ item.name === user.name ? <span className="del" onClick={ () => delComment(item.id)}>删除</span> : ''}
</p>
<p>
{item.content}
</p>
</div>
</li>
)
})
}
</ul>
</>
);
}
}
七、props-children 属性
- 组件标签的子节点(标签之间的内容,插槽),可以是任意值(文本,React元素,组件,函数)
- react实现插槽的2种方式
- 1. props传递jsx片段
- 2. props.children 读取组件之间的内容
八、props-类型校验
- 导入 import PropTypes from "prop-types";
- 使用 组件名.propTypes = {'props属性':'props校验规则’} 进行类型约定
- PropTypes 包含各种规则
import React from "react";
// 1. 导入 prop-types
import PropTypes from "prop-types";
export default function Hello(props) {
return (
<>
<div>Hello</div>
<span>
{
props.arr.map((item,index) => {
return <span key={index}>{item}</span>
})
}
</span>
</>
)
}
// 2.校验规则
Hello.prototype = {
arr:PropTypes.array
}
// 校验规则
list.prototype = {
// 语法: 属性名:PropTypes.类型函数.isRequired
// 规定optionalFunc属性值必须是函数
optionalFunc:PropTypes.func,
// 规定requiredFunc属性值必须是函数且必须传入
requiredFunc:PropTypes.func.isRequired,
// 规定size属性值的类型可以是数组或字符串
size:PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
// 个性化定义
person:PropTypes.shape({
name:PropTypes.string,
age:PropTypes.number,
say:PropTypes.func
})
}
- props默认值设置 : 组件.defaultProps ={属性名:默认值}
import React from 'react'
import PropTypes from "prop-types";
// 也可以用 参数默认值 来实现 pageNum=1
const Pagination = ({pageSize,pageNum=1}) => {
return (
<div>
Pagination
<br />
pageSize 的默认值是: {pageSize }
<br />
pageNum 的默认值是:{pageNum}
</div>
)
}
// 定义默认值
Pagination.defaultProps = {
pageSize:10
}
export default Pagination
- 静态属性
- 类名.属性 = 新值 给莫格类定义静态属性
- 类名.方法名 = 函数 给某个类定义静态方法
- 在类组件中通过 static propTypes={} 定义props校验规则
- static defaultProps ={} 定义props 默认值
import React,{Component} from "react";
// 1. 导入 prop-types
import PropTypes from "prop-types";
export default class Hello extends Component {
// 2. 定义静态属性 定义校验类型
static propTypes = {
// oneOf校验唯一 二选一
sex:PropTypes.oneOf(['男','女']).isRequired
}
// 3. 定义静态属性 默认值
static defaultProps = {
sex:'男'
}
render() {
return (
<div>
{this.props.sex}
</div>
);
}
}
// 也可以写在外面
// Hello.propTypes = {
// arr:PropTypes.oneOf(['男','女']).isRequired
// }
// Hello.defaultProps = {
// sex: '男'
// }
九、React 生命周期
- 生命周期:是从创建到最后消亡的过程
- 类组件有生命周期 函数组件没有生命周期除非使用Hooks
1.Mounting(挂载):已插入真实 DOM
钩子函数 | 触发时机 | 作用 |
---|---|---|
constructor | 创建组件时 最先执行 | 1.初始化state 2.创建ref 3. 使用bind解决this指向问题 |
render | 每次组件渲染都会触发 | 渲染UI 不能调用setState() |
componentDidMount | 组件挂载后 | 1.发送网络请求 2.DOM操作 |
import React, {Component, createRef} from "react";
export default class Hello extends Component {
// 1. 挂载阶段 初始化
constructor(){
super()
this.state = {}
this.ipt = createRef()
console.log("挂载阶段 初始化")
}
// 2. 挂载阶段 渲染
render() {
console.log("挂载阶段 渲染")
return (
<div>
Hello
</div>
);
}
// 3. 挂载阶段 渲染完成
componentDidMount() {
console.log("挂载阶段 渲染完成")
}
}
2.Updating(更新):正在被重新渲染
- componentDidUpdate(): 在更新后会被立即调用。
3.Unmounting(卸载):已移出真实 DOM
- componentWillUnmount(): 在组件卸载及销毁之前直接调用。
1. 挂载期 constructor -> render -> componentDidMount
2. 更新期 render -> componentDidUpdate
3. 销毁期 componentWillUnmount
4. 执行顺序 父组件(constructor) -> 父组件(render) -> 子组件(constructor) -> 子组件(render) -> 子组件(componentDidMount) -> 父组件(componentDidUpdate)
5. 阻止组件更新:shouldComponentUpdate(nextProps, nextState)
shouldComponentUpdate() 方法会返回一个布尔值,指定 React 是否应该继续渲染,默认值是 true, 即 state 每次发生变化组件都会重新渲染。
shouldComponentUpdate() 的返回值用于判断 React 组件的输出是否受当前 state 或 props 更改的影响,当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
十、setState 扩展
- 调用 setstate 时,将要更新的状态对象,放到一个更新队列中暂存起来(没有立即更新)
- 如果多次调用 setState 更新状态,状态会进行合并,后面覆盖前面
- 等到所有的操作都执行完毕,React 会拿到最终的状态,然后触发组件更新
import React, {Component} from 'react'
export default class Hi extends Component {
state = {
count: 0
}
handleClick = () => {
// 【重要点】:如果多次调用 setState 更新状态,状态会进行合并,后面覆盖前面
this.setState({count: this.state.count+100})
this.setState({count: this.state.count+1})
// 【重要点】:setState是异步操作!
console.log(this.state.count) // 打印0
}
render() {
console.log('render')
return (
<div>
<div>Hi组件:{this.state.count}</div>
<button onClick={this.handleClick}>体现“异步”和合并</button>
</div>
)
}
}
- 使用 setState((prevState) => {}) 可以解决多次调用状态依赖问题
- 使用 setState(updater[,callback]) 在状态更新后立即执行某个操作
import React, {Component} from 'react'
export default class Hi extends Component {
state = {
count: 0
}
handleClick = () => {
// this.setState({count: this.state.count+1})
// this.setState({count: this.state.count+1})
// this.setState({count: this.state.count+1})
// 以上写法会合并更新,本质只会执行最后1个
// 语法1: 合并更新,采用最后1个
// this.setState({
// count:this.state.count+1 // count的更新是依赖于之前的state状态!
// })
// 语法2: 在之前的状态上进行,也就是多个都会调用!
// 语法格式 this.setState( (之前的state)=>({ 新数据 }) )
this.setState(oldState=>{
return {
count:oldState.count+1
}
})
this.setState(oldState=>({ count:oldState.count+1 }),()=>{
console.log("setState执行完毕,最新的count数据是:"+this.state.count);
})
this.setState(oldState=>({ count:oldState.count+1 }),()=>{
console.log("setState执行完毕,最新的count数据是:"+this.state.count);
})
// 总结: this.setState(对象/函数,回调函数)
}
render() {
return (
<div>
<div>Hi组件:{this.state.count}</div>
<button onClick={this.handleClick}>setState串联更新数据</button>
</div>
)
}
}
- setstate本身并不是一个异步方法,其之所以会表现出一种“异步"的形式,是因为react框架本身的一个性能优化机制
- React会将多个setstate的调用合并为一个来执行,也就是说,当执行setstate的时候,state中的数据并不会马上更新
- setstate如果是在react的生命周期中或者是事件处理函数中,表现出来为:延迟合并更新(“异步更新”)
- setstate如果是在setTimeout/setlnterval或者原生事件中,表现出来是:立即更新(“同步更新”)
★ 在react事件函数或者生命周期函数表现“异步",在定时器或者原生事件中表现同步
import React, {Component} from 'react'
export default class Demo extends Component {
state = {
count: 0
}
handleClick = () => {
// 合成事件的处理函数 or 生命周期构造函数
// this.setState({count: this.state.count+1})
// this.setState({count: this.state.count+1})
// 表现异步
setTimeout(() => {
this.setState({count: this.state.count+1})
this.setState({count: this.state.count+1})
}, 0);
// 表现同步
}
render() {
console.log('render')
return (
<div>
<div>Demo组件:{this.state.count}</div>
<button onClick={this.handleClick}>同步OR异步</button>
</div>
)
}
}
- 总结 : setState (类组件)
- 作用: 用于来修改state里面的数据;一旦调用 render函数重新执行!
- 使用: this.setState(函数/对象,回调函数) 回调函数里面可以获取最终最新的state数据
- 注意点:
- 1. this.setState(对象) 多个写法调用, 合并,最终取最后一个setState
- 2. this.setState( oldState=>({新数据}) ) 多个写法调用,以此都会执行
- 3. setState 的异步问题 (生命周期、react事件函数里) 解决方法: 第二个参数回调函数里读最新值
- 4. setState 的同步问题 (定时器、原生事件里)