React 知识点(二)


一、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 的同步问题 (定时器、原生事件里)
  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是React中常用的一些知识点: 1. 组件:React将用户界面拆分为可重用的组件,组件是构建React应用的基本单元。组件可以是函数组件或者类组件。 2. JSX:JSX是一种类似于HTML的语法扩展,可以在JavaScript代码中编写类似HTML的结构。它允许我们以声明式方式描述UI组件的结构。 3. Props:Props是组件的属性,用于传递数据和配置参数给组件。通过props,我们可以向子组件传递数据并进行组件之间的通信。 4. State:State是用于存储和管理组件内部的数据的对象。当state发生变化时,React会自动重新渲染组件,并更新相应的视图。 5. 生命周期:React组件具有生命周期方法,这些方法在组件的不同阶段被调用,例如组件被创建、更新、卸载等。 6. Hooks:Hooks是React 16.8版本引入的特性,它允许我们在无需编写类组件的情况下使用状态和其他React特性。常用的Hooks包括useState、useEffect、useContext等。 7. 条件渲染:React允许我们根据条件来渲染不同的内容或组件。常用的条件渲染方式包括if语句、三元表达式和逻辑与(&&)运算符等。 8. 列表渲染:React提供了map方法来遍历数组或者列表,动态生成列表项。通过列表渲染,我们可以根据数据动态生成多个相似的组件。 9. 表单处理:React提供了一些事件和处理函数来方便地处理表单的输入和提交。通过onChange事件,我们可以监听表单元素的值变化,并将其保存到组件的state中。 10. 组件通信:React中的组件通信可以通过props、状态提升、上下文(Context)和全局状态管理工具(如Redux)等方式来实现。 这些是React中常用的知识点,当然还有更多深入的内容和特性可以学习和掌握。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值