react笔记

文章目录

一、初识

官网:https://react.docschina.org/
官方脚手架:https://create-react-app.bootcss.com/

创建react项目: npx create-react-app 项目名称

相关博客:http://47.94.210.129/malulesson/Framework/react/01.html

二、jsx

jsx = javascript + xml

1.jsx元素写法有两种:

jsx的形式
React.createElement的形式
请添加图片描述

2、jsx元素本质是一个对象,这个对象是不可变对象

  • jsx元素最终需要被渲染成真实DOM元素,所以不能直接操作"原材料",只能使用它。
  • 由class或function定义出来的叫组件,组件名必须大写
  • 由jsx语法或React.createElement返回的结果叫jsx元素,jsx元素充当组件的视图模板

3、使用{}

在jsx元素中,你要嵌套表达式,必须使用{}包起来,也就是说,在jsx元素中,看到{}你要知道里面放的都是表达式。任何有值的内容都是表达式
请添加图片描述

4、jsx也可以做了函数的入参,也就是在调用一个函数时,传递传递一个jsx元素,也可以做为函数的返回值,还可以用在if或for中。

请添加图片描述

5、jsx元素中有三个变化的属性:

  • class 变成了 className
  • for 变成了 htmlFor
  • tabindex 变成了tabIndex

6、jsx元素中新增了三个属性:

  • key 用于列表渲染
  • ref 方便DOM操作
  • dangerouslySetInnerHTML 用来渲染一片html字符串

请添加图片描述

7、jsx只能有一个根标签

8、在jsx元素,不仅组件可以使用单标签,任何html标签都可以使用单标签

9、在JSX中写行内样式

<div style={ { key1:'value1', key2:'value2',... } }></div>
  • 在JSX中,写class类名

请添加图片描述

10、JSX中,所有React组件(由class或function定义)的名称必须以大写字母打头

11、JSX语法中是支持点语法

React.Component
React.Fragment <></>
Ml.Button
Ml.Form

请添加图片描述

请添加图片描述
请添加图片描述

12、对于组件来说,props就是自定义属性

  • 只能使用它,不要去修改它

请添加图片描述
请添加图片描述

13、props.children

  • 在使用组件时,如果组件使用双标签,在双标签之间就可以写内容,在双标签之间写的内容是通过props.children来接收的,props.children可以是任意类型的数据,基本数据类型可以,引用数据类型也可以,也可以是函数

请添加图片描述
请添加图片描述
测试结果:
请添加图片描述

请添加图片描述

14、在使用JSX时,如果是数组,可以直接渲染

请添加图片描述

  • 在上面的数组中可以写哪些数据类型
    请添加图片描述
  • 如果数组中有boolean值,有null,有und,会被直接忽略,也就是说不会生成对应的文本节点。

三、状态

1、this.setState

  • this.setState({}, callback) 在修改状态时,当新值与旧值无关时,推荐使用这种写法,callback表示当状态修改后,自动执行,当状态修完后,有一些业务逻辑放到callback中。
  • this.setState((state, props)=>({}), callback) 当新值与旧值有关时,新值由旧值计算而来,形参state永远表示旧值,建议使用这种写法。callback表示当状态修改后,自动执行,当状态修完后,有一些业务逻辑放到callback中。

2、修改状态的同步和异步问题

  • 在React18之前,React16和React17,this.setState在合成事件中,是异步的。在宏任务,Promise.then中是同步的。所谓的React合成事件是指on*系列事件,生命周期函数。
  • 为什么是V18版本中,把this.setState设计成异步的?
    • 为了性能优化。在V18中,无论this.setState在哪里,都是异步的,这种特性,叫”并发模式“

3、事件绑定

请添加图片描述
上面的写法,不完美,因为在监听器中不能获取到this。this是und。解决办法有两种:

  • 利用ES5中的bind中手动绑定this
  • 利用ES6中的箭头函数自动绑定this

ES5绑定代码如下:请添加图片描述
使用ES6中的箭头函数自动绑定this
请添加图片描述
获取事件对象
请添加图片描述

4、状态在函数组件中的使用

  • 函数组件中是不能定义状态,但是配合hook可以定义状态,hook类似于vue3中的组合式API

请添加图片描述
setNum是同步还是异步,在不同的版本中是不一样的,总结如下:

  • 在V18之前,在合成事件中是异步的,在宏任务和promise.then中是同步的。
  • 在V18中,无论在哪里都是异步的。

setNum中,是没有callback。setNum还有一种写法
请添加图片描述

四、条件渲染

类组件中实现条件渲染

在React中,没有指令,使用原生JS来实现
单一元素的条件渲染

{
    flag && < jsx / >
}

请添加图片描述
请添加图片描述

两个元素,实现条件渲染
请添加图片描述
多个元素的条件渲染,建议封装成一个自定义的渲染函数
请添加图片描述
在vue中有一个v-show,是通过display控制显示与隐藏的,使用react实现之
请添加图片描述

函数组件中实现条件渲染

请添加图片描述

五、表单绑定,列表渲染

类组件实现todolist
把表单变成受控表单,收集表单中的数据
请添加图片描述
请添加图片描述
在React中,收集数据,需要写一个value,还需要写一个onInput,根据人家建议,换成onChange,
请添加图片描述
当点击添加按钮,就需要把收集到的todo存起来,如下:
请添加图片描述
把每一个todo存储到数组中,如下:
请添加图片描述

完整代码

// import { useEffect } from "react";
// import { useState } from "react";
// function Todo() {
//   let [todo, setTodo] = useState("")
//   let [list, setList] = useState(JSON.parse(localStorage.getItem('list')) || [])

//   let addtodo = () => {
//     if (!todo.trim()) {
//       alert("不能为空")
//       return
//     }
//     setList([...list, { id: Date.now(), task: todo }])
//     setTodo("")
//   }
//   let addByEnter = (e) => {
//     if (e.key === "Enter") {
//       addtodo()
//     }
//   }
//   let delTodo = (id) => {
//     setList(list.filter(item => item.id !== id))
//   }
//   useEffect(() => {
//     localStorage.setItem('list', JSON.stringify(list))
//   }, [list])
//   return (
//     <div>
//       <div>
//         <input type="text" value={todo}
//           onChange={e => setTodo(e.target.value)}
//           onKeyUp={e => addByEnter(e)}
//         />
//         <button onClick={addtodo}>添加</button>
//         <hr />
//         <div>
//           {
//             list.map(item => (
//               <div key={item.id}>
//                 <span>{item.id}</span>
//                 <span>~~</span>
//                 <span>{item.task}</span>
//                 <button onClick={() => delTodo(item.id)}>删除</button>
//               </div>
//             ))
//           }
//         </div>
//       </div>
//     </div>
//   )
// }


// export default Todo


/* 类式写法 */
import { Component } from "react";
class Todo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      Todo: "",
      list: [],
    }
  }
  getTodo(e) {
    this.setState({ todo: e.target.value })
  }
  addtodo(e) {
    if (!this.state.todo.trim()) {
      alert("不能为空!")
      return
    }
    this.setState(state => ({
      list: [...state.list, { id: Date.now(), task: state.todo }]
    }))
    this.setState({
      todo: ""
    })
  }
  addByenter(e) {
    if (e.key === "Enter") {
      this.addtodo()
    }
  }
  delTodo(id) {
    this.setState(state => ({
      list: state.list.filter(item => item.id !== id)
    }))
  }

  render() {
    let { todo, list } = this.state;
    return (
      <div>
        <div>
          <input type="text" value={todo}
            onChange={e => this.getTodo(e)}
            onKeyUp={e => this.addByenter(e)}
          ></input>
          <button onClick={e => this.addtodo(e)}>添加</button>
        </div>
        <div>
          {
            list.map(item => (
              <div key={item.id}><span>{item.id}</span>--<span>{item.task}</span>
                <button onClick={() => this.delTodo(item.id)}>删除</button>
              </div>

            ))
          }
        </div>
      </div>
    )
  }
}

export default Todo
  • 受控表单:一个表单的value或checked由state来决定,通过控制state就可以修改表单数据,这样的表单叫受控表单。
  • 受控组件:一个组件的自定义属性由状态来控制,只有这个状态发生变化,组件才能更新。后面讲。
  • 列表渲染:在React中,通过map方法实现。因为map方法,可以对数据进行加工,返回新的数据(jsx);

六、生命周期

  • 装载阶段(3个):constructor, render, componentDidMount
  • 更新阶段(2个):render,componentDidUpdate
  • 卸载阶段(1个):componentWillUnmount

请添加图片描述

constructor(装载)

请添加图片描述

在constructor中,可以完全this的绑定
请添加图片描述
请添加图片描述
在官方文档中,还有一种解决办法,就是在constructor中进行this绑定
请添加图片描述

  • 在constructor中有一个props,这个props是用来接收父传递的数据的,props的数据流和state的数据流必须独立。
  • 在constructor中,不能调用setState方法
  • 在constructor中,不能调用接口,不能进行DOM操作,不能开定时器… 一切和业务相关的代码都不能写在constructor钩子函数中。
  • 在constructor中,可以做什么:
    • 定义状态
    • 绑定方法的this

render(装载)

  • 作用:用于返回组件的视图结构,这个结构在vue叫模板,在react中叫jsx。它的背后是生成一棵Fiber树,在vue中叫虚拟DOM,在React叫Fiber树,早期的React也是使用的虚拟DOM。
  • 在render函数中,不能调用setState
  • 请添加图片描述
    请添加图片描述
    render是一个函数,这个函数返回一个jsx,jsx叫jsx元素,本质是一个对象,创建jsx元素有2种方法,一种是直接使用jsx,另外一种是React.creeateEleement。也就说,调用render函数,会生成棵Fiber树,类似于Vue中的虚拟DOM,这个Fiber树是双向链表结构。生成这课Fiber树的过程是异步的,生成的过程是可以中断,能不能中断是看浏览器忙不忙,如果浏览器比较忙,就可能中断,等待浏览器不忙时,会继续生成,直到Fiber树创建完成。然后会进行协调运算,这个协调运算类似于Vue中的Diff运算,也就是一棵老的Fiber树和一棵新的Fiber树进行对比运算。运算对比后,就会进入到commmit提交阶段,一次性提交Fiber更新DOM。

componentDidMount(装载)

类似于vue中的mounted,表示页面第一次渲染完成。在这个钩子函数中可以做什么?

  • 调用接口
  • 开定时器
  • DOM操作
  • 编写业务逻辑

请添加图片描述

render(更新)

请添加图片描述
请添加图片描述

componentDidUpdate(更新)

  • 相当于Vue中的updated,表示页面再次渲染成功。
  • 监听器是用来监听数据是否变化,updated表示数据变化了,会执行updated,也就是说数据变化了,在updated也可以监听到了。要监听数据变化,在vue中,使用监听器比使用updated更方便。在react中是没有监听器的概念的,在React中实现类似于Vue的监听器的功能,需要使用compoentDidUpdate钩子函数了。

请添加图片描述

  • 如果不使用componentDidUpdate,还有没有办法实现类似于Vue中的监听器?

    • 答:this.setState({}/fn, callback) 利用callback也可以感知到数据变化了。推荐使用componentDidUpdate。因为多个setState会合并,合并后,callback也会出问题
  • 你要想在componentDidUpdate中调用setState,需要给出一个出口,不给出口,直接爆栈

  • React实现Vue中的监听器

请添加图片描述
总结:

  • componentDidUpdate中可以调用setState,但是必须给出出口(终止条件),否则会产生死循环,循环到一定次数就会报错。
  • componentDidUpdate可以模拟vue中的监听器,特别需要注意终止条件。
  • 除了使用componentDidUpdate实现监听器之外,还可以使用this.setState中的callback来实现,不建议使用,因为setState会合并,callback容易出问题。

componentWillUnmount(卸载)

类似于Vue中的beforeDestroy,表示组件即将销毁。

  • 清缓存
  • 清除定时器
  • 关闭长连接
  • 销毁DOM元素

shouldComponentUpdate(了解)

它是控制更新阶段的开关,就是来控制是否更新,当返回true就正常更新,当返回false时就不更新。
请添加图片描述
请添加图片描述
默认情况下,没有写的话,就是返回true,看一下state参数
请添加图片描述
shouldComponentUpdate,返回true时,正常更新,返回false时,不执行更新阶段。注意

  • 当执行forceUpdate时,会绕过shouldComponentUpdate方法,一定会进入到更新阶段。
  • shouldComponentUpdate,可以使用PureCompoentf替代

请添加图片描述
组件中有很多状态,有些状态会参与到界面刷新,也就是说有些状态变了,需要更新页面。但是还有一些状态是不参与到界面更新,也就是状态变了,不需要更新页面的,此时就体现出开关的重要性了。参与页面更新的状态,状态变化了,在showCompoentUpdate中返回true,正常更新。如果没有参与页面刷新的状态变化了,在shouldCompoentUpdate中返回false,就需要再次调用render。这样,就会少生成一次Fiber树。这个钩子函数是用来性能调优的,可以阻塞掉那些不参与视图渲染的状态更新导致的Fiber生成。

  • React组件渲染(更新)流程,由两个阶段组成的,一个叫render阶段,一个叫commit阶段,如下:
    请添加图片描述

  • render阶段:

    • 目标是生成Fiber树,这个过程是异步的,是可中断,并且不会执行任何副作用。到底中断与否,看的是浏览器主线程的忙不忙。
  • commit阶段:

    • 目的是把协调运算的结果,一次性提交渲染或更新真实DOM。这个过程在V18之前是不可中断的,在V18中是可以人为中断的。

七、状态提升

  • 什么是状态提升
  • 一个父组件,下面有两个子组件,这两个子组件,需要通信,通常会把这个状态定义到父组件中,通过父传子,再通过子传父,实现兄弟之间的通信,状态提供到父上面,就是所谓的状态提升。
  • 在react中没有所谓的自定义属性或自定义事件,都是所谓props。也就是说研究状态提升就是研究所谓的父子通信。

官方小案例

请添加图片描述
把状态提升到父组件中,传递给子组件
请添加图片描述
实现子传父,在React中实现如下:
请添加图片描述
父组件中的代码是可以简写的请添加图片描述
把温度状态传递给华氏温度组件,如下请添加图片描述
华氏温度组件接收,转化,使用,如下:
请添加图片描述
现在改变华氏温度
请添加图片描述

import { Component, useState } from "react"
class Fahrenheit extends Component {
    render() {
        const { value, onChange } = this.props
        let change = (e) => {
            // 得到最新的华氏温度,需要回传给父组件
            // console.log(e.target.value);
            // 把华氏温度变成摄氏温度,回传给父组件
            let temper = (Number(e.target.value) - 32) * 5 / 9;
            onChange(Number(temper))
        }
        return (
            <div>
                <label>华氏温度:</label>
                <input type="text" value={value * 9 / 5 + 32} onChange={change} />
            </div>
        )
    }
}
function Celsius(props) {
    let { value, onChange } = props;
    let change = (e) => {
        onChange(e.target.value);
    }
    return (
        <div>
            <label>摄氏温度:</label>
            <input type="text" value={value} onChange={change} />
        </div>
    )
}
function DemoA() {
    const [temper, setTemper] = useState(0);
    return (
        <div>
            <Fahrenheit value={temper} onChange={val => setTemper(val)}></Fahrenheit>
            <hr />
            <Celsius value={temper} onChange={val => setTemper(val)}></Celsius>
        </div>
    )
}

export default DemoA

总结:

  • 所谓的状态提升,就是把一个状态提升到父组件中,实现父传子,实现子传父。

  • 在React中,一般不讨论自定义属性或自定义事件,写在组件上的都叫props。props的值可以是基本数据,也可以是对象,也可以是数组,当然也可以是函数,也可以是jsx。

  • 如果是函数,函数分两类,一类叫事件函数,另一类叫渲染函数。如果是事件函数,建议以on打头,如果是渲染函数,建议不要使用on打头。

  <Model a="hello" b={110} onChange={()=>{}} footer={()=>(<footer />)}></Model>
  onChange={()=>{}} 叫事件函数
  footer={()=>(<footer />)}  叫渲染函数

八、封装一个组件

使用组合的思想封装弹窗组件

样式.scss

.ml-layer {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-color: rgba(0, 0, 0, 0.6);

    .ml-modal {
        width: 520px;
        background: white;
        border-radius: 3px;
        position: absolute;
        top: 120px;
        left: 50%;
        margin-left: -260px;
        box-sizing: border-box;

        header {
            line-height: 50px;
            padding: 0 20px;
            border-bottom: 1px solid #eee;
            font-size: 12px;

            &>div {
                width: 100%;
                height: 100%;
                overflow: hidden;

                &>div:first-child {
                    float: left;
                }

                &>div:last-child {
                    float: right;
                    cursor: pointer;
                }
            }
        }

        footer {
            line-height: 50px;
            padding: 0 20px;
            border-top: 1px solid #eee;
            font-size: 12px;
            height: 50px;
            overflow: hidden;

            &>.ml-button {
                float: right;
            }
        }

        main {
            box-sizing: border-box;
            padding: 20px;
            font-size: 14px;
        }
    }
}

.ml-button {
    display: inline-block;
    cursor: pointer;
    margin: 0 15px;

    span {
        display: inline-block;
        height: 30px;
        box-sizing: border-box;
        line-height: 28px;
        font-size: 12px;
        border-radius: 2px;
        padding: 0 15px;
        border: 1px solid transparent;
    }

    span.default {
        border-color: #ccc;
    }

    span.primary {
        color: white;
        background: blue;
        border-color: blue;
    }

    span.danger {
        color: white;
        background: red;
        border-color: red;
    }

    span.info {
        color: white;
        background: green;
        border-color: green;
    }
}

代码


import "../asets/modal.scss"
import PropTypes from "prop-types"
import { useState } from "react"

function Button(props) {
  let { type, children } = props
  return (
    <div className="ml-button">
      <span className={type}>{children}</span>
    </div>
  )
}
Button.propTypes = {
  type: PropTypes.oneOf(["default", "primary", "danger", "info"]),
  children: PropTypes.node
}
Button.defaultProps = {
  type: "default",
  children: "按钮"
}

function Modal(props) {
  let { title, closeable, children, type, onChange } = props
  let click = (e) => {//点击遮罩层关闭弹窗
    // console.log(123);
    // console.log(e.target.className == "ml-layer");
    e.target.className === "ml-layer" && onChange()
  }
  let opt = (e) => {//判断点击按钮做出相关执行
    // console.log(e.target.className);
    if (e.target.className === "default") {
      onChange()
    } else if (e.target.className === type) {
      console.log(type);
      onChange()
    }

  }
  let renderFooter = () => {
    let btns = []
    switch (type) {
      case "primary":
        btns = [
          <Button type="primary" key="1">确定</Button>,
          <Button key="2" >取消</Button>
        ]
        break;
      case "danger":
        btns = [
          <Button type="danger" key="1">取消</Button>,
          <Button key="2" >取消</Button>
        ]
        break;
      case "info":
        btns = [
          <Button type="info" key="1">确定</Button>,
        ]
        break;
    }
    return btns
  }

  return (
    <div className="ml-layer" onClick={click}>
      <div className="ml-modal">
        <header>
          <div>
            <div> {title}</div>
            <div onClick={() => onChange()}> {closeable && "x"}</div>

          </div>
        </header>
        <main>{children}</main>
        <footer onClick={opt}>
          {/* <Button type="primary">确定</Button>
          <Button type="danger">取消</Button> */}
          {renderFooter()}
        </footer>

      </div>
    </div>
  )
}
//验证
Modal.propTypes = {
  title: PropTypes.string,
  closable: PropTypes.bool,
  children: PropTypes.node,
  type: PropTypes.oneOf(["primary", "danger", "info"])

}

// 非必传项,需要提供默认值
Modal.defaultProps = {
  title: "默认小标题",
  closable: true,
  children: <div>主体默认内容</div>,
  type: "info"
}
function PageA() {
  let [flag, setFlag] = useState(false)
  let change = () => {
    // console.log(flag);
    setFlag(!flag)
  }
  return (
    <div>
      <button onClick={() => setFlag(true)}> open modal</button>
      {flag && <Modal title={"添加用户"} closeable type="primary" onChange={change}>
        <div>
          <input type="text" />
          <div>你确认添加此用户么</div>
        </div>

      </Modal >}
    </div >
  )

}

export default PageA

效果
在这里插入图片描述++++

import PropTypes from "prop-types"
import "@/assets/style.scss"
import { useState } from "react"

// <Button type="promary">确定</Button>
function Button(props) {
    let { type, children, onClick } = props;
    return (
        <div className="ml-button">
            {/* onClick 叫React的合成事件 */}
            <span className={type} onClick={onClick}>{children}</span>
        </div>
    )
}
Button.propTypes = {
    type: PropTypes.oneOf(["default", "primary", "danger", "info"]),
    children: PropTypes.node,
    onClick: PropTypes.func,
}
Button.defaultProps = {
    type: 'default',
    children: '按钮',
    onClick: () => { },
}

function Modal(props) {
    let { title, closable, children, type, visiable, onCancel, onOk, footer } = props;
    // 根据不同的type,渲染出不同的按钮组
    // 写渲染函数
    let renderFooter = () => {
        let btns = [];
        switch (type) {
            case "confirm":
                btns = [
                    <Button type="primary" key="1" onClick={onOk}>确定</Button>,
                    // Button是组件, onClick是事件函数  是一个特殊的props
                    <Button key="2" onClick={onCancel}>取消</Button>
                ]
                break;
            case "danger":
                btns = [
                    <Button type="danger" key="1" onClick={onOk}>删除</Button>,
                    <Button key="2" onClick={onCancel}>取消</Button>
                ]
                break;
            case "info":
                btns = [
                    <Button type="info" key="1" onClick={onCancel}>我知道了</Button>,
                ]
                break;
        }
        return btns;
    }
    let handerLayer = (e) => {
        // target表示你实打实点击的那个元素
        // console.log(e.target);
        // currentTarget表示点击那个元素所在的最外层元素
        // console.log(e.currentTarget);

        // console.log(e.target.dataset.self);
        if (e.target.dataset.self) {
            onCancel()
        }
    }
    return (
        <div className="ml-layer" style={{ display: visiable ? 'block' : 'none' }} data-self="layer" onClick={handerLayer}>
            <div className="ml-modal">
                <header>
                    <div>
                        <div>{title}</div>
                        <div onClick={onCancel}>{closable && "X"}</div>
                    </div>
                </header>
                <main>
                    {
                        children
                    }
                </main>
                <footer>
                    {/* 使用渲染函数 */}
                    {/* {renderFooter()} */}
                    {footer ? footer() : renderFooter()}
                </footer>
            </div>
        </div>
    )
}
Modal.propTypes = {
    title: PropTypes.elementType, // jsx, string, null,und, func
    closable: PropTypes.bool,
    children: PropTypes.node,
    type: PropTypes.oneOf(["confirm", "danger", "info"]),
    visiable: PropTypes.bool,
    onCancel: PropTypes.func,
    onOk: PropTypes.func,
    footer: PropTypes.func
}

Modal.defaultProps = {
    title: "默认小标题",
    closable: true,
    children: <div>主体内容默认值</div>,
    type: "info",
    visiable: false,
    onCancel: () => { },
    onOk: () => { },
    footer: () => { }
}

function PageA() {
    // 定义一个状态控制弹窗是否显示
    let [visiable, setVisiable] = useState(false)
    let submit = () => {
        setTimeout(() => {
            console.log("submit...");
            console.log("发送ajax请求...");

            setVisiable(false)
        }, 200)
    }
    return (
        <div>
            <button onClick={() => setVisiable(true)}>open modal</button>
            {/* 如果一个props是一个事件函数,建议使用on打头 */}
            <Modal
                title={"添加用户"}
                closable
                type="confirm"
                visiable={visiable}
                onCancel={() => setVisiable(false)}
                onOk={submit}
                footer={
                    () => {
                        return [
                            <Button type='danger' key="1">残忍离开</Button>,
                            <Button type='primary' key="2">确定</Button>
                        ]
                    }
                }
            >
                <div>
                    <input type="text" />
                    <div>你确定要添加此用户吗?</div>
                </div>
            </Modal>
        </div>
    )
}
export default PageA;

拆分后代码

import PropTypes from "prop-types"
import "@/assets/style.scss"
import { useState } from "react"

// <Button type="promary">确定</Button>
function Button(props) {
    let { type, children, onClick } = props;
    return (
        <div className="ml-button">
            {/* onClick 叫React的合成事件 */}
            <span className={type} onClick={onClick}>{children}</span>
        </div>
    )
}
Button.propTypes = {
    type: PropTypes.oneOf(["default", "primary", "danger", "info"]),
    children: PropTypes.node,
    onClick: PropTypes.func,
}
Button.defaultProps = {
    type: 'default',
    children: '按钮',
    onClick: () => { },
}

function ModalHeader(props) {
    let { title, closable, onCancel } = props;
    return (
        <div>
            <div>{title}</div>
            <div onClick={onCancel}>{closable && "X"}</div>
        </div>
    )
}
function ModalFooter(props) {
    let { type, onCancel, onOk, footer } = props;
    let renderFooter = () => {
        let btns = [];
        switch (type) {
            case "confirm":
                btns = [
                    <Button type="primary" key="1" onClick={onOk}>确定</Button>,
                    // Button是组件, onClick是事件函数  是一个特殊的props
                    <Button key="2" onClick={onCancel}>取消</Button>
                ]
                break;
            case "danger":
                btns = [
                    <Button type="danger" key="1" onClick={onOk}>删除</Button>,
                    <Button key="2" onClick={onCancel}>取消</Button>
                ]
                break;
            case "info":
                btns = [
                    <Button type="info" key="1" onClick={onCancel}>我知道了</Button>,
                ]
                break;
        }
        return btns;
    }
    return (
        footer ? footer() : renderFooter()
    )
}

function Modal(props) {
    let { children, visiable, onCancel, width } = props;
    let handerLayer = (e) => {
        if (e.target.dataset.self) {
            onCancel()
        }
    }
    return (
        <div className="ml-layer" style={{ display: visiable ? 'block' : 'none' }} data-self="layer" onClick={handerLayer}>
            <div className="ml-modal" style={{ width: `${width}px`, marginLeft: `-${width / 2}px` }}>
                <header>
                    {/* 叫props穿透 */}
                    <ModalHeader {...props}></ModalHeader>
                </header>
                <main>
                    {
                        children
                    }
                </main>
                <footer>
                    <ModalFooter {...props}></ModalFooter>
                </footer>
            </div>
        </div>
    )
}

Modal.propTypes = {
    title: PropTypes.elementType, // jsx, string, null,und, func
    closable: PropTypes.bool,
    children: PropTypes.node,
    type: PropTypes.oneOf(["confirm", "danger", "info"]),
    visiable: PropTypes.bool,
    onCancel: PropTypes.func,
    onOk: PropTypes.func,
    footer: PropTypes.func,
    width: PropTypes.oneOfType([
        PropTypes.number,
        PropTypes.string
    ])
}

Modal.defaultProps = {
    title: "默认小标题",
    closable: true,
    children: <div>主体内容默认值</div>,
    type: "info",
    visiable: false,
    onCancel: () => { },
    onOk: () => { },
    footer: () => { },
    with: 520
}

function PageA() {
    // 定义一个状态控制弹窗是否显示
    let [visiable, setVisiable] = useState(false)
    let submit = () => {
        setTimeout(() => {
            console.log("submit...");
            console.log("发送ajax请求...");

            setVisiable(false)
        }, 200)
    }
    return (
        <div>
            <button onClick={() => setVisiable(true)}>open modal</button>
            {/* 如果一个props是一个事件函数,建议使用on打头 */}
            <Modal
                title={"添加用户"}
                closable
                type="confirm"
                visiable={visiable}
                onCancel={() => setVisiable(false)}
                onOk={submit}
                footer={
                    () => {
                        return [
                            <Button type='danger' key="1" onClick={() => setVisiable(false)}>残忍离开</Button>,
                            <Button type='primary' key="2">确定</Button>
                        ]
                    }
                }
                width={800}
            >
                <div>
                    <input type="text" />
                    <div>你确定要添加此用户吗?</div>
                </div>
            </Modal>
        </div>
    )
}
export default PageA;

什么是组合?

组合是React组件化的设计模式。也就是研究如何封装一个组件,步骤如下:

  • 第一步:根据UI设计图拆解组件。
  • 第二步:把这个独立的组件单独进行封装。
  • 第三步:利用props把组件串联起来。

九、上下文

1、什么是上下文

在React中,利用上下文,进行组件间的通信。类似于Vue中的proveder/inject。
特点:

  • 在组件树中,上下文中一种单向数据流通信,不能颠倒。
  • 通信是可以跨级的,祖先提供数据,后代消费数据。
  • 这个通过方法,不具有响应式。

创建对应的组件
请添加图片描述
上下文是为了实现通信,需要创建一个上下文
请添加图片描述
使用上下文,有两种方案,1、
请添加图片描述
先在祖先中提供数据
请添加图片描述
看Child能不能获取数据
请添加图片描述
上面是我们使用上下文的方式一,还有一种方式,是推荐的
请添加图片描述
总结使用上下文的步骤:

第一步:创建const ThemeContent = React.createContext()创建上下文
第二步:使用Provider提供数据,是给后代提供数据
第三步:消费上下文中的数据有两种方案。

小案例

import React, { PureComponent, useState } from "react";
const ThemeContext = React.createContext();
// Provider是用来提供数据
// Consumer是用来消费数据
const { Provider, Consumer } = ThemeContext

// 上下文只能类组件才有
class Child extends PureComponent {
    render() {
        console.log(this);
        return (
            <Consumer>
                {
                    (ctx) => {
                        return (
                            <div style={ctx}>
                                <h3>我是孩子组件</h3>
                            </div>
                        )
                    }
                }
            </Consumer>

        )
    }
}

function Parent(props) {
    return (
        <div>
            <h3>父组件</h3>
            <Child />
        </div>
    )
}

function PageA() {
    let obj = { a: 1 }
    // 在JSX中是不能直接渲染一个对象,但是可以渲染一个数组
    const [theme, setTheme] = useState({ color: "#000000", background: "#ffffff" })
    let change = (key, e) => {
        setTheme({ ...theme, [key]: e.target.value })
    }
    return (
        <Provider value={theme}>
            <div>
                <div>
                    <h2>页面</h2>
                    <hr />
                    <Parent />
                </div>
                <div>
                    前景色: <input type="color" value={theme.color} onChange={e => change('color', e)} />
                    背景色: <input type="color" value={theme.background} onChange={e => change('background', e)} />
                </div>
            </div>
        </Provider>
    )
}

export default PageA

效果如下 :
请添加图片描述
把颜色切换,可以封装成一个组件,如下:

import React, { PureComponent, useState } from "react";
const ThemeContext = React.createContext();
// Provider是用来提供数据
// Consumer是用来消费数据
const { Provider, Consumer } = ThemeContext

// 上下文只能类组件才有
class Child extends PureComponent {
    render() {
        console.log(this);
        return (
            <Consumer>
                {
                    (ctx) => {
                        return (
                            <div style={ctx}>
                                <h3>我是孩子组件</h3>
                            </div>
                        )
                    }
                }
            </Consumer>

        )
    }
}

function Parent(props) {
    return (
        <div>
            <h3>父组件</h3>
            <Child />
        </div>
    )
}

function ThemeToggle({ theme, onChange }) {
    let change = (e) => {
        let key = e.target.name;
        let val = e.target.value;
        onChange({ ...theme, [key]: val })
    }
    return (
        <>
            <div>
                前景色: <input type="color" name="color" value={theme.color} onChange={change} />
                背景色: <input type="color" name="background" value={theme.background} onChange={change} />
            </div>
        </>
    )
}

function PageA() {
    let obj = { a: 1 }
    // 在JSX中是不能直接渲染一个对象,但是可以渲染一个数组
    const [theme, setTheme] = useState({ color: "#000000", background: "#ffffff" })
    let change = (key, e) => {
        setTheme({ ...theme, [key]: e.target.value })
    }
    return (
        <Provider value={theme}>
            <div>
                <div>
                    <h2>页面</h2>
                    <hr />
                    <Parent />
                </div>
                {/* onChange={e => setTheme(e)} 叫事件函数 */}
                <ThemeToggle theme={theme} onChange={e => setTheme(e)}></ThemeToggle>
            </div>
        </Provider>
    )
}

export default PageA

效果如下:
请添加图片描述

  • 上下文的使用场景:

    • 路由中会使用到上下文
    • 状态管理中也会使用到上下文
    • 在一些组件库,如切换主题,切换组件大小… 也会使用到上下文
    • 在际化中,也会使用到上文
  • 总结React中的组件通信:

    • 状态提升(父传子,子传父),核心靠props
    • 上下文,是祖先与后代之间的通信,父子关系不需要明确
    • props穿透,需要搞清楚,父子关系,缺点:会让后代的props变得臃肿。

十、高阶组件

HOC = Higher Order Compoent。高阶组件是类组件编程中一种非常重要的代码逻辑复用技巧。

请添加图片描述

可以使用ES6中的装饰器语法
请添加图片描述
高阶组件可以简写,如下:
请添加图片描述
foo就可以叫高阶组件,也叫高阶函数,接收一个组件作为入参,经过一翻装饰(仅仅是装饰),最后返回一个新的组件。对入参组件是不能修改的。

前面返回的新组件是一个类组件,现在这让它返回函数组件,如下:
请添加图片描述
请添加图片描述
简写
请添加图片描述
请添加图片描述
请添加图片描述
高阶组件的定义方式通常有两种:

第一种:const hoc = (WC) => props => (&lt;jsx/>)     使用: hoc(DemoA)
第二步:const hoc = (...arg) => WC => props => (&lt;jsx/>)   使用:hoc(1,2)(DemoA)

可以装饰多次,如下:请添加图片描述
把上面装饰的写法,换成函数调用如下:请添加图片描述
请添加图片描述
DemoA组件接收的props,
请添加图片描述
请添加图片描述
想获取所有的props,如下:
请添加图片描述
上面的foo和bar都可以叫高阶组件,使用:

  • 在类组件上可以通过 hoc(DemoA) , 还可以通过ES6中的装饰器
  • 在函数组件上,只能通过 hoc(DemoA)

高阶组件的应用

  • 高阶组件应用的也非常多,如下:

    • 路由中的withRouter()
    • Redux中的connect()
    • Mobx中的inject和observer()
    • 利用高阶组件,可以给每个项目添加公司的视图结构
    • 实现后面管理系统中的水印
    • 实现权限校验
    • 把重复的业务功能封装到高阶组件,如:埋点,DOM操作,BOM操作…

封装一个高阶组件,可以给不同的页面添加不同的title,如下:
请添加图片描述

十一、Ref和Ref转发

1,Ref写在DOM元素上

请添加图片描述

2,Ref写在子组件上

请添加图片描述
注:不能写在函数组件上

Ref转发

如果把一个ref写在函数组件上,会后错,此时就需要使用ref转发,ref转发目的是为了获取函数式组件中的DOM元素。如下:
请添加图片描述

12、Hooks

1.什么是hook

hook是React官方提供的API

  • 作用:用于在函数式组件中模拟出类组件的功能。如:state,生命周期,ref,上下文…
  • 价值:有了hook,我们就可以不再使用类组件。官方说了,并不是要淘汰类组件。hook不能出现在类组件。
  • 哪些:useState, useEffect, useLayoutEffect,useContext, userReducer, useRef, useMemo, useCallback…
  • 开源hook: react-use, ahooks…

2.useState

useState是用于在函数式组件中定义状态的。
请添加图片描述

3.useEffect

effect是副作用的意思。作用:是用来模拟类组件中的生命周期的,不是模拟所有的生命周期,是模拟这三个componentDidMount/componentDidUpdate/componentWillUnmount生命周期。

类似于vue中的watchEffect,vue中的watchEffect会自动依赖依赖,React中的useEffect,需要手动指定依赖。

语法:

useEffect(() => {
    fn1();
    return fn2()
}, [依赖数组])

在fn1中写一些副作用,在fn2中清除副作用。
useEffect的工作流程:

  • 当没有“依赖数组”这个参数时,初始化只执行fn1,当re-render时,先执行fn2,再执行fn1。当路由切换时,只执行fn2。
  • 当有“依赖数组”这个参数时,但是是一个空数组,初始化只执行fn1,当re-render时,什么也不执行。当路由切换时,只执行fn2。
  • 当有“依赖数组”这个参数时,但是不是一个空数组,初始化只执行fn1。有且仅有当“依赖数组"中的变量发生变化导致rerender时,先执行fn2,再执行fn1()。当路由切换时,只执行fn2。

语法总结:

  • 格式:useEffect(()=>{fn1, return fn2}, [依赖数组])
  • 情况一:没有依赖数组,初始化时,执行fn1,rerender时,先fn2,再fn1。路由切换只执行fn2。
  • 情况二:有依赖数组,是一个空数组。初始化时,执行fn1,rerender时,fn2和fn1都不执行。路由切换只执行fn2。
  • 情况三:有依赖数组,依赖数组中有状态。初始化时,执行fn1,只有依赖数组中的状态变化了,rerender时,先fn2,再fn1。路由切换只执行fn2。
  • fn1相当于类组件中的componentDidMount
  • fn2相当于类组件中的componentWillUnMount
  • 依赖数组,相当于类组件中的componentDidUpdate

注意

  • 在一个函数式组件中,可以书写多个useEffect,多个useEffect,它们是彼此不影响
  • useEffect是用来执行副作用,建议一个useEffect只执行一个副作用。不要在同一个useEffect中同时执行多个副作用。
  • 在函数式组件中,不要把副作用直接暴露在函数体内,一定要使用useEffect进行控制

请添加图片描述

4.useLayoutEffect

  • 运行机制和useEffect是一样的,区别在于useLayoutEffect执行时候更早
  • 在这个hook中,不能进行ref或dom操作。

5.useContext

函数式组件中是没有上下文的,使用useContext,就可以在函数式组件中使用上下文
语法:

const ctx = useContext(上下文对象)

请添加图片描述
请添加图片描述

6.useRef

如果ref写在函数组件标签上,会报错。需要使用ref转发,转发到了函数式组件中的JSX中的DOM标签上。进而获取函数式组件中的JSX中的DOM元素。
请添加图片描述

7.useMemo

作用:用于性能优化,用于把一些比较消耗性能的计算进行缓存,类似于vue中的计算属性。
语法:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • 特点:有且仅有当”依赖数组"中的状态发生变化了,useMemo才会重新执行那个昂贵的计算。

  • 注意:useMemo的第二个参数是依赖数且,也有三种写法,和useEffect一样。

  • computeExpensiveValue表示昂贵计算。

请添加图片描述

8.useCallback

作用:用于性能优化,用来缓存一个函数声明。
可以使用useMemo去代替useCallback
请添加图片描述
语法:

const fn = useCallback(() => {}, [依赖数组])

9.useReducer

useState是用来定义状态的,如果有很多的状态,通过需要写多个useState。使用useReducer有一个好处,一个useReducer可以代替多个useState。是在React函数式组件中模块Redux的数据流。
语法:

const [state, dispath / foreUpdate / setState] = useReducer(reducer, {
    初始值
})
import { useState, useEffect, useReducer } from "react"

// state表示状态,action表示信号,根据不同的信号,就可以针对性地修改状态
// reducer 是管理员的意思,要修改状态必须通过reducer
// 管理员根据信号进行状态的修改
// 修改状态的流程:1)对于state进行深copy  2)修改更新state  3)返回修改后的state
const reducer = (state, action) => {
    let newState = JSON.parse(JSON.stringify(state)); // 1)对于state进行深copy
    // 根据信号更新state
    switch (action.type) {  // action是一个对象,对象中有一个type,不同的type表示不同的信号
        case "NUM_ADD":
            newState.num += 1
            break;
        case "NUM_SUB":
            newState.num -= 1
            break;
    }
    return newState;
}
// 定义初始值
const initState = {
    num: 1,
    list: ["a", "b"],
    falg: true
}

const A = props => {
    // dispatch 是用来派发一个action,管理员就可以收到action
    const [state, dispatch] = useReducer(reducer, initState);
    return (
        <div>
            <h2 >函数组件</h2>
            <h3>{state.num}</h3>
            <h3>{state.list}</h3>
            <button onClick={() => dispatch({ type: "NUM_ADD" })}>+1</button>
            <button onClick={() => dispatch({ type: "NUM_SUB" })}>-1</button>
        </div>
    )
}
export default A

10.useId

作用:返回一个唯一的标识,在函数式组件的整个运行过程中都是唯一的。
请添加图片描述

11.useDeferredValue

作用:和防抖类似。和真正的防抖区别在于,这个hook所延迟的时间是不确定,由浏览器自己决定。请添加图片描述

13、路由

安装路由:

cnpm i react-router-dom -S

路由相关的有三个包:

  • react-router 路由核心包
  • react-router-dom 基于浏览器的路由包,专用于做web开发
  • react-router-native 基于RN平台的路由包,专用于原生APP开发

请添加图片描述

14、antd组件库

官网:https://ant.design/docs/react/introduce-cn

安装antd中的字体图标

cnpm install --save @ant-design/icons

15、React实现管理系统

参考网址:
http://47.94.210.129/malulesson/Framework/react/13.html#%E4%B8%80-%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98

练习:https://gitee.com/lcb522/react_ant_admin

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值