React 知识点(二)

文章目录

  • [一、React 组件](#一、React 组件)
  • [二、React 组件通信 - 父子通信](#二、React 组件通信 - 父子通信)
  • [三、React 组件通信 - 子父通信](#三、React 组件通信 - 子父通信)
  • [四、React 组件通信 - 兄弟通信](#四、React 组件通信 - 兄弟通信)
  • [五、React 组件通信 - 跨组件通信(祖先)](#五、React 组件通信 - 跨组件通信(祖先))
  • 六、结合组件通信案例
  • [七、props-children 属性](#七、props-children 属性)
  • 八、props-类型校验
  • [九、React 生命周期](#九、React 生命周期)
  • [十、setState 扩展](#十、setState 扩展)

一、React 组件

1. 函数组件:使用JS函数创建的组件

注:函数名称首字母必须大写;函数必须要有返回值,如果没有可以返回null

javascript 复制代码
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

javascript 复制代码
import React from 'react';

export default function  Header() {
    return (
        <div>
            头部
        </div>
    );
};

Footer.js

javascript 复制代码
import React from 'react';

const Footer = () => {
    return (
        <div>
            底部
        </div>
    );
};

export default Footer;

Loading.js

javascript 复制代码
import React from 'react';

const Loading = () => {
    const loading = true
    return loading ? <div>加载中...</div> : null
};

export default Loading;

index.js

javascript 复制代码
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

javascript 复制代码
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

javascript 复制代码
import React, {Component} from 'react';

export default class Header extends Component {
    render() {
        return (
            <div>
                Header
            </div>
        );
    }
}

index.js

javascript 复制代码
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. 定义状态

javascript 复制代码
// 定义 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+事件名称={处理函数} 的方式绑定事件,事件名称需要遵循 大驼峰 规则。
- 处理函数默认的参数就是事件对象,可以使用事件对象处理默认行为和事件冒泡。
javascript 复制代码
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 绑定
javascript 复制代码
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
javascript 复制代码
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函数执行,模板也就更新,展示出最新的数据
javascript 复制代码
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
javascript 复制代码
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事件

javascript 复制代码
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 获取表单元素获取非受控组件的值。
javascript 复制代码
// 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

javascript 复制代码
<link href="https://at.alicdn.com/t/font_2998849_vtlo0vj7ryi.css" rel="stylesheet"/>

App.js

javascript 复制代码
import { Component } from 'react';
import Comment from './components/Comment.jsx';
class App extends Component {
    render() {
        return (
            <>
                <Comment />
            </>
        );
    }
}
export default App;

index.js

javascript 复制代码
import React from 'react';

import ReactDOM from 'react-dom';

import App from './App.js'

ReactDOM.render(<App/>,document.getElementById('root'))

src/components/index.css

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

javascript 复制代码
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

javascript 复制代码
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

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

javascript 复制代码
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

javascript 复制代码
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

javascript 复制代码
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

javascript 复制代码
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 包含各种规则
javascript 复制代码
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
}
javascript 复制代码
// 校验规则
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 ={属性名:默认值}
javascript 复制代码
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 默认值 
javascript 复制代码
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操作
javascript 复制代码
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 会拿到最终的状态,然后触发组件更新
javascript 复制代码
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]) 在状态更新后立即执行某个操作
javascript 复制代码
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事件函数或者生命周期函数表现"异步",在定时器或者原生事件中表现同步
javascript 复制代码
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 的同步问题 (定时器、原生事件里)
相关推荐
10年前端老司机1 小时前
什么!纯前端也能识别图片中的文案、还支持100多个国家的语言
前端·javascript·vue.js
摸鱼仙人~1 小时前
React 性能优化实战指南:从理论到实践的完整攻略
前端·react.js·性能优化
程序员阿超的博客2 小时前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 2452 小时前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax
小小小小宇7 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖8 小时前
http的缓存问题
前端·javascript·http
小小小小宇8 小时前
请求竞态问题统一封装
前端
loriloy8 小时前
前端资源帖
前端
源码超级联盟8 小时前
display的block和inline-block有什么区别
前端
GISer_Jing8 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js