• React基礎


    一. 安装React

    • npm install -g create-react-app

    二. 创建项目

    • create-react-app 项目名称

    三. 启动

    • yarn start

    四. 查看webpack配置

    • yarn eject

    五. 组件的定义方式

    1. 使用class关键字创建组件的特点:
      • 使用class关键字创建的组件,有自己的私有数据(this.state)和生命周期函数;
      • 用class关键字创建出来的组件叫做有状态组件【用的最多】
    2. 使用function关键字创建组件的特点:
      • 使用function创建的组件,只有props, 没有自己的私有数据和生命周期函数;
      • 用构造函数创建出来的组件:叫做无状态组件【无状态组件用的不多】

    无关的东西

    const flag = false; 区分 自动播放(false) 和 拖动播放(true)
    
    if(!flag) 等价于 (flag === false) { //  if(flag) 拖动了  if(!flag)没有拖动
    
    ...逻辑
    
    }
    
    

    六.生命周期

    (Mounting)挂载

    1. constructor

      • 通过给 this.state赋值对象来初始化内部的state;

      • 为事件绑定实例(this);

    2. rander

      这个钩子函数虽然是组件的渲染方法,但并不是真正意义上的把元素渲染成dom的方法,在它之后还有一个把元素渲染成dom的步骤,这一步只是return一个元素对象,这个并不是组件的执行渲染功能的函数,
      • 渲染组件 第一次进入时会触发
    3. componentDidMount

      componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。

      • 依赖于DOM的操作可以在这里进行;
      • 在此处发送网络请求最好的地方;(官方建议)
      • 可以在此处添加一些订阅(会在componentWillUnmount取消订阅)

      image-20210611163752592

    (Updating)更新时

    1. componentDidUpdate

      componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。

      • 当组件更新后,可以在此处对 DOM 进行操作;
      • 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)

    image-20210611164907067

    (Unmounting)卸载

    componentWillUnmount() 会在组件卸载及销毁之前直接调用。

    1. componentWillUnmount

      • 在此方法中执行必要的清理操作;
      • 例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等;

      image-20210611170737790

    不常用生命周期

    除了上面介绍的生命周期函数之外,还有一些不常用的生命周期函数:

    • getDerivedStateFromProps:state 的值在任何时候都依赖于 props时使用;该方法返回一个对象来更新state;
    • getSnapshotBeforeUpdate:在React更新DOM之前回调的一个函数,可以获取DOM更新前的一些信息(比如说滚动位置);
    • shouldComponentUpdate:该生命周期函数很常用,但是我们等待讲性能优化时再来详细讲解;

    详细描述:

    image-20210611170952660

    image-20210611171216903

    七. 父传子通信-父传子

    1. class类组件

      /*
      父组件: 通过自定义属性进行传值
      子组件: 通过this.props接收数据
       */
      
      // 子组件
      class Son extends Component {
        constructor(props) { // 这个constructor 可以有可以无, 因为内部有默认的constructor
          super(props);      // constructor接受到参数, 然后通过props传给父类,
          this.props = props // super虽然代表父类的构造函数, 但是返回的是子类的实例, 即super内部的this指的是子类
        }
        render() {
          let {name, age, sex} = this.props // 此时的 this指的是子类的this
          return (
            <div>
              <h2>子组件接受父组件传来的数据: {name + " " + age + " " + sex}</h2>
            </div>
          )
        }
      }
      
      // 父组件
      export default class App extends Component {
        render() {
          return (
            <div>
              <Son name="chenlong" age="23" sex="男"></Son>
            </div>
          )
        }
      }
      
    2. function组件

      // 子组件
      function Son(props) {
        const {name, age, sex} = props
        return (
          <div>
            <h2>{name + " " + age + " " + sex}</h2>
          </div>
        )
      }
      
      // 父组件
      export default class App extends Component {
        render() {
          return (
            <div>
              <Son name="chen" age="23" sex="男"></Son>
            </div>
          )
        }
      }
      

    八. 父传子通信-属性验证

    • 使用 PropTypes 类型检查

      import PropTypes from 'prop-types';
      
    1. function写法

      function Son(props) {
        const { name, age, sex} = props
      
        return (
          <div>
            <h2>{name + age + sex}</h2>
          </div>
        )
      }
      
      Son.propTypes = {
        name: PropTypes.string,
        age: PropTypes.number,
        sex: PropTypes.string
      }
      
    2. class类写法

      class Son extends Component{
        static propTypes = { // 属性验证
          name: PropTypes.string,
          age: PropTypes.number,
          sex: PropTypes.string
        }
      
        static defaultProps = { // 默认数据
          name:'王小虎',
          age: '24',
          sex: '男'
        }
      
        render() {
          let {name, age, sex} = this.props
          return (
            <div>
              <h2>{name + age + sex}</h2>
            </div>
          )
        }
      }
      
    3. 验证没通过报错:

      image-20210612012548007

    九一. 父传子通信-子传父

    /* 
    父组件: 给子组件传递一个属性名, 并且绑定自己的函数
    子组件: 通过触发this.props.父组件传递的属性名('需要传的值')
    */
    
    class Son extends PureComponent {
    render() {
      return (
        <div>
          <button onClick={e => {this.handlePush()}}>我是子组件</button> // 1.子组件创建一个事件
        </div>
      )
    }
      handlePush(){
        this.props.SonComment('我是子组件传来的值') // 3. 子组件调用父组件传来的SonComment函数, 并且携带需要传的值
      }
    }
    
    export default class App extends PureComponent {
      render() {
        return (
          <div>
            <Son SonComment ={info => this.submitComment(info)} /> // 2.父组件自定义一个属性名SonComment
          </div>
        )
      }
      submitComment(val){ // 4.由于SonComment 绑定了submitComment事件,所以可以拿到子组件传来的值
        console.log('父组件-----' + val)
      }
    }
    
    

    九二. 父传子通信-子调用父组件的方法

    /* 
    子组件: 通过接受父组件传递过来的事件名字, 给自己绑定上
    父组件: 传递一个事件名字
    */
    
    //写法1 子组件
    class Son extends Component {
      render() {
        const { add } = this.props // 3.接受父组件传递过来的事件名字, 给自己绑定上 
        return (
          <div>
            <button onClick={add}>子组件调用父组件+1</button> // 4.绑定在自己身上 
          </div>
        )
      }
    }
    
    //写法2 子组件
    class Son extends Component {
      render() {
        const { add } = this.props // 3.接受父组件传递过来的事件名字, 给自己绑定上 
        return (
          <div>
            <button onClick={this.addComment}>子组件调用父组件+1</button> // 4.绑定在自己身上 
          </div>
        )
      }
    }
    
    
    // 父组件
    export default class App extends Component {
      constructor(props){
        super(props)
    
        this.state = {
          counter: 0
        }
      }
      render() {
        return (
          <div>
            <h2>{this.state.counter}</h2>
            <button onClick={e => {this.add()}}>父组件+1</button>    
            {/* 2.给子组件传递一个叫add 的值(其实传的是一个函数) */} 
            <Son add={e => {this.add()}}></Son>   
          </div>
        )
      }
      add(){ // 1.先创建一个事件
        this.setState({
          counter: this.state.counter+1
        })
      }
    }
    
    

    十. Slot组件插槽

    1. children实现
      • 每个组件都可以获取到 props.children:它包含组件的开始标签和结束标签之间的内容。

        比如

      • 如果只有一个元素,那么children指向该元素;

      • 如果有多个元素,那么children指向的是数组,数组中包含多个元素;

      • 弊端:通过索引值获取传入的元素很容易出错,不能精准的获取传入的原生;

      实现方式:

      // 父组件
      export default class App extends Component {
        render() {
          return (
            <div>
              <h2>父组件:</h2>
              <Navbar>
                <span>slot-left</span>
                <span>slot-center</span>
                <span>slot-right</span>
              </Navbar>
            </div>
          )
        }
      }
      
      // 子组件
      export default class Navbar extends Component {
        render() {
          return (
            <div className="nav-wrap">
              <div className="nav-item nav-left">
                {this.props.children[0]} {/* 通过 props.children取值 */}
              </div>
              <div className="nav-item nav-center">
              {this.props.children[1]}
              </div>
              <div className="nav-item nav-right">
              {this.props.children[2]}
              </div>
            </div>
          )
        }
      }
      
      

      源码分析:

      image-20210612131717171

    2. props实现
      • 通过具体的属性名,可以让我们在传入和获取时更加的精准;

      实现方式:

      // 父组件 App.js
      export default class App extends Component {
        render() {
          const slotLeft = <div>slot-left</div>
          const slotCenter = <div>slot-center</div>
          const slotRight = <div>slot-right</div>
          return (
            <div>
              <h2>父组件:</h2>
              <h2>props实现:</h2>
              <Navbar2 slotLeft={slotLeft} slotCenter={slotCenter} slotRight={slotRight}></Navbar2>
            </div>
          )
        }
      }
      
      // 子组件 Navbar.js
      export default class Navbar2 extends Component {
        render() {
          const { slotLeft, slotCenter, slotRight } = this.props
          return (
            <div className="nav-wrap">
              <div className="nav-item nav-left">
                {slotLeft}
              </div>
              <div className="nav-item nav-center">
                {slotCenter}
              </div>
              <div className="nav-item nav-right">
                 {slotRight}
              </div>
            </div>
          )
        }
      }
      
      

    十一.跨组件通信

    1. 中间组件层层传递

      • 但是对于有一些场景:比如一些数据需要在多个组件中进行共享(地区偏好、UI主题、用户登录状态、用户信息等)。
      • 如果我们在顶层的App中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的操作。
      // 第二层组件
      function Navbar(props) {
        return (
          <div>
            <h2>用户昵称: {props.nickName}</h2>
            <h2>用户等级: {props.level}</h2>
          </div>
        )
      }
      // 第一层组件
      class Main extends Component {
        render() {
          return (
            <div>
              <Navbar nickName={this.props.nickName} level={this.props.level}></Navbar>
              <ul>
                <li>设置1</li>
                <li>设置2</li>
                <li>设置3</li>
                <li>设置4</li>
              </ul>
            </div>
          )
        }
      }
      // 父组件
      export default class App extends Component {
        constructor(props){
          super(props)
          this.state = {
            nickName : 'chen',
            level: 99
          }
        }
        render() {
          const { nickName, level } = this.state
          return (
            <div>
              <Main nickName={nickName} level={level}></Main>
              <h2>其他内容</h2>
            </div>
          )
        }
      }
      

      我这边顺便补充一个小的知识点:Spread Attributes

      属性展开:

      如果你已经有了一个 props 对象,你可以使用展开运算符 ... 来在 JSX 中传递整个 props 对象。以下两个组件是等价的:

    image-20210612160421579

    但是,如果层级更多的话,一层层传递是非常麻烦,并且代码是非常冗余的:

    • React提供了一个API:Context;
    • Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props;
    • Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言
    2. Context相关的API

    2.1 React.createContext 第一步

    const MyContext = React.createContext(defaultValue);
    
    • 创建一个需要共享的Context对象:

    • 如果一个组件订阅了Context, 那么这个组件会从离自身最近的那个匹配的 Provider中读取到当前的context值

    • defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值(可以在里面放对象)

      2.2 Context.Provider 第二步

    <MyContext.Provider value={/* 某个值 */}> // value: 代表需要共享的值
    
    • 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:

    • Provider 接收一个 value 属性,传递给消费组件;

    • 一个 Provider 可以和多个消费组件有对应关系;

    • 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;

    • 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染;

      2.3 Class.contextType 第三步

    挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:

    • 这能让你使用 this.context 来消费最近 Context 上的那个值;
    • 你可以在任何生命周期中访问到它,包括 render 函数中;
    MyClass.contextType = MyContext // myclass组件要订阅--> MyContext上的context(也就是共享出来的值)
    

    源码

    image-20210612175623209

    由于context是源码里已经声明了, 所以可以直接使用这个属性

    ↑↑↑ 以上是class式组件订阅context的写法

    2.4 Context.Consumer 函数式组件订阅context的方法

    • 这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context。

    • 这里需要 函数作为子元素(function as child)这种做法;

    • 这个函数接收当前的 context 值,返回一个 React 节点;

      <MyContext.Consumer>
        {value => /* 基于 context 值进行渲染*/}
      </MyContext.Consumer>
      
    3. Context使用过程
    • class写法:
      import React, { Component } from 'react'
      
      // 1.创建一个共享的Context对象:
      const UserContext = React.createContext({ 
        nickName: '小陈', // 默认值
        level: -1
      })
      
      // 孙组件
      class Navbar extends Component{
        render() {
          console.log(this.context)
          return (
            <div>
              <h2>用户昵称: {this.context.nickName}</h2> // 消费context里面的值
              <h2>用户等级: {this.context.level}</h2>
            </div>
          )
        }
      }
      
      Navbar.contextType = UserContext; // 3.把 <UserContext.Provider>组件 value绑定的值,传给Navbar组件, 然后Navbar可以通过 context拿到传来的数据
      
      // 子组件
      class Main extends Component {
        render() {
          return (
            <div>
              <Navbar ></Navbar>
              <ul>
                <li>设置1</li>
                <li>设置2</li>
                <li>设置3</li>
                <li>设置4</li>
              </ul>
            </div>
          )
        }
      }
      
      // 父组件
      export default class App extends Component {
        constructor(props){
          super(props)
          this.state = {
            nickName : 'chen',
            level: 99
          }
        }
        render() {
          return (
            <div>
              {/* 2.接受一个 value属性, 传递个消费组件 */}
              <UserContext.Provider value={this.state}>
                <Main></Main> 
              </UserContext.Provider>
              <h2>其他内容</h2>
            </div>
          )
        }
      }
      
      
    • function函数组件写法:

      什么时候使用Context.Consumer呢?

      • 1.当使用value的组件是一个函数式组件时;
      • 2.当组件中需要使用多个Context时;
      演练一:
      import React, { Component } from 'react'
      
      // 1.创建一个共享的Context对象:
      const UserContext = React.createContext({ 
        nickName: '小陈', // 默认值
        level: -1
      })
      
      // 孙组件
      function Navbar() { 
        return (            /*3.使用 Consumer消费订阅的值  */
          <UserContext.Consumer> 
            {
              value => { /* 4.基于context进行渲染 */
                return (
                  <div>
                    <h2>用户昵称: {value.nickName}</h2>
                    <h2>用户等级: {value.level}</h2>
                  </div>
                )
              }
            }
          </UserContext.Consumer>
        )
      }
      
      // 子组件
      class Main extends Component {
        render() {
          return (
            <div>
              <Navbar ></Navbar>
              <ul>
                <li>设置1</li>
                <li>设置2</li>
                <li>设置3</li>
                <li>设置4</li>
              </ul>
            </div>
          )
        }
      }
      
      // 父组件
      export default class App extends Component {
        constructor(props){
          super(props)
          this.state = {
            nickName : 'chen',
            level: 99
          }
        }
        render() {
          return (
            <div>
              {/* 2.接受一个 value属性, 传递个消费组件 */}
              <UserContext.Provider value={this.state}>
                <Main></Main> 
              </UserContext.Provider>
              <h2>其他内容</h2>
            </div>
          )
        }
      }
      
      
      演练二:
      import React, { Component } from 'react'
      
      // 1.创建一个共享的Context对象:
      const UserContext = React.createContext({ 
        nickName: '小陈', // 默认值
        level: -1
      })
      
      // 2.创建第二个共享的Context对象
      const ColorContext = React.createContext({
        color:'pink'
      })
      
      // 孙组件
      function Navbar() { 
        return (            /*4.使用 Consumer消费订阅的值  */
          <UserContext.Consumer> 
            {
              value => { /* 5.多个contenxt进行渲染 */
                return (
                  <ColorContext.Consumer>
                    {
                      item =>{
                        return (
                          <div>
                            <h2>用户昵称: {value.nickName}</h2>
                            <h2>用户等级: {value.level}</h2>
                            <h2>颜色: {item.color}</h2>
                          </div>
                        )
                      }
                    }
                  </ColorContext.Consumer>
                )
              }
            }
          </UserContext.Consumer>
        )
      }
      
      // 子组件
      class Main extends Component {
        render() {
          return (
            <div>
              <Navbar ></Navbar>
              <ul>
                <li>设置1</li>
                <li>设置2</li>
                <li>设置3</li>
                <li>设置4</li>
              </ul>
            </div>
          )
        }
      }
      
      // 父组件
      export default class App extends Component {
        constructor(props){
          super(props)
          this.state = {
            nickName : 'chen',
            level: 99
          }
        }
        render() {
          return (
            <div>
              {/* 3.把值, 传递个消费组件 */}
                <UserContext.Provider value={this.state}>
                  <ColorContext.Provider value={{color: "red"}}>
                    <Main></Main> 
                  </ColorContext.Provider>
                </UserContext.Provider>
              <h2>其他内容</h2>
            </div>
          )
        }
      }
      
      

    image-20210713231700465

    十二. setState详细解析 和 React性能优化

    1.setState

    image-20210612201645992

    解决控制台打印异步问题:
    this.setState({
      list: [...this.state.list,info]
    },() =>{
     console.log(this.state.list)
    })
    
    2.SCU优化
    • 在App中,我们增加了一个计数器的代码;

    • 当点击+1时,会重新调用App的render函数;

    • 而当App的render函数被调用时,所有的子组件的render函数都会被重新调用;

      import React, { Component } from 'react';
      
      function Header() {
        console.log("Header Render 被调用");
        return <h2>Header</h2>
      }
      
      class Main extends Component {
        render() {
          console.log("Main Render 被调用");
          return (
            <div>
              <Banner/>
              <ProductList/>
            </div>
          )
        }
      }
      
      function Banner() {
        console.log("Banner Render 被调用");
        return <div>Banner</div>
      }
      
      function ProductList() {
        console.log("ProductList Render 被调用");
        return (
          <ul>
            <li>商品1</li>
            <li>商品2</li>
            <li>商品3</li>
            <li>商品4</li>
            <li>商品5</li>
          </ul>
        )
      }
      
      function Footer() {
        console.log("Footer Render 被调用");
        return <h2>Footer</h2>
      }
      
      export default class App extends Component {
        constructor(props) {
          super(props);
      
          this.state = {
            counter: 0,
            message:'hello world'
          }
        }
      
      /*  shouldComponentUpdate(nextProps, nextState) {
          if (nextState.counter !== this.state.counter) {
            return true;
          }
        
          return false;
        }
       */
      
        render() {
          console.log("App Render 被调用");
      
          return (
            <div>
              <h2>当前计数: {this.state.counter}</h2>
              <button onClick={e => this.increment()}>+1</button>
              <button onClick={e => this.changeText()}>改变文本</button>
              <Header/>
              <Main/>
              <Footer/>
            </div>
          )
        }
      
        increment() {
          this.setState({
            counter: this.state.counter + 1
          })
        }
        changeText() {
          this.setState({
            message: "你好啊,李银河"
          })
        }
      }
      

      image-20210614180428122

      在以后的开发中,我们只要是修改了App中的数据,所有的组件都需要重新render,进行diff算法,性能必然是很低的:
      • 事实上,很多的组件没有必须要重新render;
      • 它们调用render应该有一个前提,就是依赖的数据(state、props)发生改变时,再调用自己的render方法;
      如何来控制render方法是否被调用呢?
      • 通过shouldComponentUpdate方法即可;
    2.1 shouldComponentUpdate

    如何来控制render方法是否被调用呢?

    • 通过shouldComponentUpdate方法即可;
    shouldComponentUpdate(nextProps, nextState) {
      if (nextState.counter !== this.state.counter) {
        return true;
      }
    
      return false;
    }
    

    这个时候,我们可以通过实现shouldComponentUpdate来决定要不要重新调用render方法:

    • 这个时候,我们改变counter时,会重新渲染;
    • 如果,我们改变的是message,那么默认返回的是false,那么就不会重新渲染;
    2.2 PureComponent和memo
    • 类组件使用 pureComponent来实现

    如果所有的类,我们都需要手动来实现 shouldComponentUpdate,那么会给我们开发者增加非常多的工作量。

    我们来设想一下shouldComponentUpdate中的各种判断的目的是什么?

    • props或者state中的数据是否发生了改变,来决定shouldComponentUpdate返回true或者false;

    事实上React已经考虑到了这一点,所以React已经默认帮我们实现好了,如何实现呢?

    • 将class基础自PureComponent。

    把所有 Component 改成 PureComponent :

    import React, { PureComponent } from 'react';
    
    function Header() {
      console.log("Header Render 被调用");
      return <h2>Header</h2>
    }
    
    class Main extends PureComponent {
      render() {
        console.log("Main Render 被调用");
        return (
          <div>
            <Banner/>
            <ProductList/>
          </div>
        )
      }
    }
    

    image-20210614190159189

    PureComponent的原理是什么呢?

    • 对props和state进行浅层比较;

    查看PureComponent相关的源码:

    react/ReactBaseClasses.js中:

    • 在PureComponent的原型上增加一个isPureReactComponent为true的属性

    image-20210614190659937

    React-reconcilier/ReactFiberClassComponent.js:

    image-20210614191900072

    这个方法中,调用 !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState),这个shallowEqual就是进行浅层比较:

    • function组件使用memo来实现

    我们需要使用一个高阶组件memo:

    import React, { PureComponent,memo } from 'react';
    
    const MemoHeader = memo(function Header() {
      console.log("Header Render 被调用");
      return <h2>Header</h2>
    })
    
    

    image-20210614194143768

    十三. setState不可变的力量

    PureComponent使用了性能优化, 如果数据没发生变化, rander不会重新更新

      handleName(val){
        const newData = [...this.state.list]
        newData[val].age +=1
        this.setState({
          list: newData
        })
      }
    

    十四. 全局事件传递(bus)

    兄弟组件传值

    yarn add events
    
    /*
     * @Date: 2021-06-23 22:07:26
     */
    import React, { Component, PureComponent } from 'react'
    import { EventEmitter} from 'events'
    
    // 事件总线: event bus
    const eventBus = new EventEmitter()
    
    class  SonOne extends PureComponent {
    
      componentDidMount(){// 页面加载监听的事件, 以及参数
        // eventBus.addListener('getName',(name,age) => { 
        // })
        eventBus.addListener('getName', this.handleGetNameListener) // 常用写法
      }
    
      componentDidUpdate(){ // 页面卸除 取消事件监听
        eventBus.removeListener("getName",this.handleGetNameListener)
      }
    
      handleGetNameListener(name,age){
        console.log(name,age)
      }
    
      render() {
        return (
          <div>
            sonOne
          </div>
        )
      }
    }
    class  SonTwo extends PureComponent {
      render() {
        return (
          <div>
            SonTwo
            <button onClick={e =>this.emmitEvent()}>点击了SonTwo</button>
          </div>
        )
      }
      emmitEvent(){
        eventBus.emit("getName","陈龙",23) // 触发一个消息
      }
    }
    
    export default class App extends PureComponent {
      render() {
        return (
          <div>
            <SonOne></SonOne>
            <SonTwo></SonTwo>
          </div>
        )
      }
    }
    

    十五. 全局事件传递(bus)

    1. 通过ref操作DOM
    import React, { createRef, PureComponent } from 'react'
    
    export default class App extends PureComponent {
      constructor(props){
        super()
    
        this.titleRef = createRef()
        this.titleEl = null
      }
      render() {
        return (
          <div>
            {/* 方法一: ref */}
            <h2 ref="title">String Ref</h2>
    
            {/* 方式二: React.createRef() */}
            <h2 ref={this.titleRef}>Hello Create Ref</h2>
    
            {/* 方式三: 传入一个函数 */}
            <h2 ref={element => this.titleEl = element}>改变文本</h2>
    
            <button onClick={e => this.changeText()}>改变文本</button>
          </div>
        )
      }
      changeText(){
        this.refs.title.innerHTML = "你好啊,猜猜猜"
        this.titleRef.current.innerHTML = "你好啊,猜猜猜"
        this.titleEl.innerHTML = "你好啊,猜猜猜"
      }
    }
    
    2. 通过ref触发子组件的方法
    export default class App extends PureComponent {
      constructor(props) {
        super(props);
    
        this.counterRef = createRef()
      }
    
      render() {
        return (
          <div>
            <Counter ref={this.counterRef}/>
            <button onClick={e => this.increment()}>app +1</button>
          </div>
        )
      }
    
      increment() { // 父组件调用子组件的方法
        this.counterRef.current.increment();
      }
    }
    

    函数式组件是没有实例的,所以无法通过ref获取他们的实例:

    • 但是某些时候,我们可能想要获取函数式组件中的某个DOM元素;
    • 这个时候我们可以通过 React.forwardRef ,后面我们也会学习 hooks 中如何使用ref;
    3.受控组件

    在 HTML 中,表单元素(如<input><textarea><select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。

    而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。

    • 我们将两者结合起来,使React的state成为“唯一数据源”;
    • 渲染表单的 React 组件还控制着用户输入过程中表单发生的操作;
    • 被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”;

    例如,如果我们想让前一个示例在提交时打印出名称,我们可以将表单写为受控组件:

    image-20210627235635592

    十五.高阶组件

    image-20210629153033677

    1.1 高阶组件的使用
    import React, { PureComponent } from 'react'
    
    class App extends PureComponent { // 函数
      render() {
        return (
          <div>
            app {this.props.name}
          </div>
        )
      }
    }
    
    /* 高阶组件函数方式 */
    function enhanceComponent(WrapperComponent){ // 1.高阶组件
      function NewComponent(props){
          return <WrapperComponent {...props}/>
      }
      return NewComponent
    }
    
    const EnhanceComponent = enhanceComponent(App) // 3. 调用高阶组件
    
    export default EnhanceComponent
    
    1.2 . 利用高阶组件, 实现跨组件通信
    const UserContext = React.createContext({
      nickName: '小陈', // 这里代表默认数据
      level: -1
    })
    
    /* 封装的一个高阶函数, 专门实现夸组件通信 */
    function withUser(WrapperCpn){
      return function(props){
        return (
          <UserContext.Consumer>
            {
              value => {
                return <WrapperCpn {...props} {...value} />
              }
            }
          </UserContext.Consumer>
        )
      }
    }
    
    /* class类的写法 */
    class Home extends PureComponent {
      render(){
        return <h2> Home组件 {"昵称" + this.props.nickName + '年龄' + this.props.age + '性别' + this.props.gander}</h2>
      }
    }
    
    /* 函数写法 */
    function Footer(props){
      const { nickName, age, gander} = props
      return <h2> Footer函数组件 {"昵称" + nickName+ '年龄' + age + '性别' + gander}</h2>
    }
    
    const UserHome = withUser(Home)
    const UserFooter = withUser(Footer)
    
    1.3 渲染判断鉴权(类似访问权限)
    /* 高阶函数鉴权 */
    function withAuto(WrapperComponent){
    const NewCpn =  function(props){
        if(props.isLogin){ // 如果权限为真,就显示传来的组件页面
          return <WrapperComponent {...props}/> // 接受到的组件
        }else{
          return <Login/>
        }
      }
      NewCpn.displayName = "AutoCpn" // 组件重命名
      return NewCpn 
    }
    
    /* class写法 */
    class Login extends PureComponent { // 登录
      render(){
        return <h2>请先登录!!!</h2>
      }
    }
    
    const AutoCartPage = withAuto(Cart) // 任何需要鉴权的页面都可以通过  调用 withAuto来鉴权
    
    <AutoCartPage isLogin={true}/>{/* 购物车页面 */}
    
    

    image-20210629215954300

    1.4 声明周期劫持(获取组件渲染时间)
    function withTime(WrapperComponent){
      return class extends PureComponent{
        UNSAFE_componentWillMount(){ // 即将渲染获取一个时间 begin
          this.begin = Date.now()
        }
        componentDidMount(){ // 渲染完成再获取一个时间 end
          this.end = Date.now()
          const interval = this.end - this.begin
          console.log(`${WrapperComponent.name}渲染的时间为:${interval}`)// 获取传来的组件名字
        }
        render() {
          return (
            <div>
              <WrapperComponent {...this.props}/>
            </div>
          )
        }
      }
    }
    

    高阶函数(HOC)的意义

    image-20210629224914373

    十六.组件内容补充

    1.1 ref的转发 forwardRef

    image-20210629230703498

    /* function组件可以使用 forwardRef高阶组件 */
    const Son  = forwardRef(function(props,ref){
      return (
        <h2 ref={ref}>我是function函数组件使用ref</h2>
      )
    })
    
    1.2 Portals的使用

    Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

    image-20210629233550337

    例子: 全局弹窗modal

    1.添加一个节点

    image-20210629234624827

    2.使用ReactDom渲染节点

    import React, { PureComponent } from 'react'
    import ReactDom from 'react-dom'
    
    class Modle extends PureComponent {
      constructor(props){
        super(props)
      }
      render() {
        return ReactDom.createPortal(
          this.props.children, // 参数1: props.children 可以拿到组件里放的所有子节点
          document.getElementById('modal') // 参数2: 是要渲染到哪一个DOM上
        )
      }
    }
    
    export default class App extends PureComponent {
      render() {
        return (
          <div>
            <Modle>
             <h2>我是全局弹窗</h2>
            </Modle>
          </div>
        )
      }
    }
    
    
    1.3 Fragment (类似于vue 的template )

    Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点

          <div>
            <p>我是节点,有div包裹</p>
            <> {/* <> 是 Fragment的简写 短语发  */}
              <h2>下面的使用了 Fragment没有div包裹</h2>
              <div>
                {
                  list.map((item,index) => {
                    return (
                      <Fragment key={item.name}>
                        <div>{item.name}</div>
                        <p>{item.age}</p>
                        <hr/>
                      </Fragment>
                    )
                  })
                }
              </div>
            </>
          </div>
    
    1.4 StrictMode

    StrictMode 是一个用来突出显示应用程序中潜在问题的工具。

    • Fragment 一样,StrictMode 不会渲染任何可见的 UI;
    • 它为其后代元素触发额外的检查和警告;
    • 严格模式检查仅在开发模式下运行;它们不会影响生产构建

    可以为应用程序的任何部分启用严格模式:

    • 会对 HeaderFooter 组件运行严格模式检查;
    • 但是,ComponentOneComponentTwo 以及它们的所有后代元素都将进行检查;
    import React from 'react';
    
    function ExampleApplication() {
      return (
        <div>
          <Header />
          <React.StrictMode>
            <div>
              <ComponentOne />
              <ComponentTwo />
            </div>
          </React.StrictMode>
          <Footer />
        </div>
      );
    }
    

    image-20210630210430356

    十七.React中的样式选择

    1.1 相比而言,React官方并没有给出在React中统一的样式风格:
    • 由此,从普通的css,到css modules,再到css in js,有几十种不同的解决方案,上百个不同的库;

    • 最好的或者说最适合自己的CSS方案,

      • 方案一:内联样式的写法;
        • 内联样式的优点:
          • 1.内联样式, 样式之间不会有冲突
          • 2.可以动态获取当前state中的状态
        • 内联样式的缺点:
          • 1.写法上都需要使用驼峰标识
          • 2.某些样式没有提示
          • 3.大量的样式, 代码混乱
          • 4.某些样式无法编写(比如伪类/伪元素)
      • 方案二:普通的css写法;
        • 如果我们按照普通的网页标准去编写,那么也不会有太大的问题;
        • 但是组件化开发中我们总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响;
        • 但是普通的css都属于全局的css,样式之间会相互影响;
      • 方案三:css modules;
        • 但是这种方案也有自己的缺陷:
          • 引用的类名,不能使用连接符(.home-title),在JavaScript中是不识别的;
          • 所有的className都必须使用{style.className} 的形式来编写;
          • 不方便动态来修改某些样式,依然需要使用内联样式的方式;
      • 方案四:css in js(styled-components);
        • 原理就是创建一个样式组件 ,该组件渲染之后是一个自己定义的标签

        • Wrapper组件跟其余的react组件一样,只不过现在他们有了自己的样式

        • CSS-in-JS通过JavaScript来为CSS赋予一些能力,包括类似于CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修改状态等等;

        • 依然CSS预处理器也具备某些能力,但是获取动态状态依然是一个不好处理的点;

        • 所以,目前可以说CSS-in-JS是React编写CSS最为受欢迎的一种解决方案;

    这里选择方案四做演示:

    1.2 css in js使用方法

    1.2.1 标签模板字符串(介绍)

    可以通过护板字符串的方式对一个函数进行调用

        const name = "chen"
        const age = "23"
        function test(...args){
          console.log(args)
        }
    
        test`aaa` //  ["aaa", raw: Array(1)]
    
        test`my name is ${name} , age is ${age}`
    

    image-20210630232119281

    1.2.2 安装styled-components:
    yarn add styled-components
    
    1.2.3 styled-components

    1.vscod css提示插件: vscode-styled-components

    image-20210630233856081

    1.2.4 styled-components语法
    1. 基本语法
    /*
     * @Date: 2021-06-30 23:01:53
     */
    import React, { PureComponent } from 'react'
    import styled from 'styled-components'
    
    const HomeWrapper =  styled.div`
      font-size:50px;
      color: red;
      .banner {
        background-color: pink;
        span {
          background-color: green;
          margin-right: 20px;
          cursor: pointer;
          &.avtive{
            background-color: red;
            color: #fff;
          }
          &:hover{
            color: #fff;
          }
        }
      }
    
    `
    
    const TitleWrapper = styled.h4` /* 1.设置一个h2的标签样式(类名如果重复可以用此方法解决) */
      text-decoration: underline;
    `
    
    export default class Home extends PureComponent {
      render() {
        return (
          <HomeWrapper>
            <TitleWrapper>我是home标题</TitleWrapper>{/* 2.使用专属标签 */}
            <div className="banner">
              <div>我是轮播图</div>
              <span className="avtive">1轮播图</span>
              <span>2轮播图</span>
              <span>3轮播图</span>
              <span>4轮播图</span>
            </div>
    
          </HomeWrapper>
        )
      }
    }
    
    
    1. 一些标签自带的属性用法

      第一种写法:

      直接在标签上写属性

      import styled from 'styled-components'
      
      const InputWrapp = styled.input`
        background-color: lightblue;
        border-color: red;
      `
      export default class About extends PureComponent {
        render() {
          return (
            <div>
              <h2>我是about标题</h2>
              <InputWrapp placeholder="请输入文字"></InputWrapp> {/* 直接在标签上写属性  */}
            </div>
          )
        }
      }
      

      第二种写法进阶:

      好处:

      • props穿透
      • attrs的使用
      • 传入state作为props属性

      可以使用 this.state对象的属性

      import styled from 'styled-components'
      
      const InputWrapp = styled.input.attrs({ // attributes:的缩写, 属性的意思
        placeholder:'请输入密码',
        bColor:'blue' // 定义一个属性值
      })`
        background-color: lightblue;
        border-color: ${props =>props.bColor}; // 1.可以拿到 attrs里面定义的 属性值, 
        color:${props => props.color};// 2. 可以动态拿到 this.state对象里面的属性
      `
      export default class About extends PureComponent {
        constructor(props){
          super(props)
          this.state = {
            color: 'red'
          }
        }
        render() {
          return (
            <div>
              <h2>我是about标题</h2>
              <InputWrapp placeholder="请输入文字"></InputWrapp>
              <br/>
              <InputWrapp color={this.state.color}></InputWrapp>
            </div>
          )
        }
      }
      
      1. 继承样式

        image-20210701002946445

        const CLbutton = styled.button`
          padding:  10px 20px;
          border-radius: 10px;
          border-color: red;
          color: red;
        `
        
        const CLbutton2 = styled(CLbutton)` // 继承CLbutton的样式
          border-color: yellow;
        `
        
        export default class App extends PureComponent {
          render() {
            return (
              <div>
                <h2>App</h2>
                <Home/>
                <About/>
                <p>样式的继承</p>
                <CLbutton>我是按钮</CLbutton>
                <CLbutton2>我是按钮2</CLbutton2>
              </div>
            )
          }
        }
        
        

      4.主题设置(全局设置样式)

      import styled, { ThemeProvider} from 'styled-components' // 1.引用 ThemeProvider主题提供器
      
      const CLbutton = styled.button`
        padding:  10px 20px;
        border-radius: 10px;
        border-color: red;
        color: red;
      `
      
      const CLbutton2 = styled(CLbutton)` // 继承CLbutton的样式
        border-color: yellow;
        color:${props => props.theme.themeColor}; // 3. 使用全局属性
      `
      
      export default class App extends PureComponent {
        render() {
          return (
            <ThemeProvider theme={{themeColor:'#01bd83',fontSize:'30px'}}> {/*2. 定义全局属性 */}
              <h2>App</h2>
              <Home/>
              <About/>
              <p>样式的继承</p>
              <CLbutton>我是按钮</CLbutton>
              <CLbutton2>我是按钮2</CLbutton2>
            </ThemeProvider>
          )
        }
      }
      

    十八.Classnames组件通过透出一个对象来匹配配置类名

    React动态添加样式

    npm install classnames --save
    yarn add classnames // 或者
    
    import classname from 'classnames'
    
    <h4 className={classname("AA","BB","CC","DD")}>动态添加类名</h4>
    <h4 className={classname({"active":isActive, "bar": isBar}, "wrap")}>动态添加类名</h4>
    <h4 className={classname("AA",errClass,{"active":isActive})}>动态添加类名</h4>
    

    十九. AntDesign UI

    1.2. AntDesign的安装
    npm install antd --save
    
    import {Button ,DatePicker } from 'antd' // 组件
    import 'antd/dist/antd.css' // 样式
    
    1.3 高级配置
    1.3.1修改create-react-app 的默认配置.

    那么如何来进行修改默认配置呢?社区目前有两个比较常见的方案:

    • react-app-rewired + customize-cra;(这个是antd早期推荐的方案)
    • craco;(目前antd推荐的方案)
    1.3.2 第一步: 安装creco
    yarn add @craco/craco
    
    1.3.3 第二部: 修改package.json文件
    • 原本启动时,我们是通过react-scripts来管理的;
    • 现在启动时,我们通过craco来管理;
    "scripts": {
    -   "start": "react-scripts start",
    -   "build": "react-scripts build",
    -   "test": "react-scripts test",
    +   "start": "craco start",
    +   "build": "craco build",
    +   "test": "craco test",
    }
    
    1.3.4 第三部: 在根目录下创建craco.config.js文件用于修改默认配置 (类似于vue.config.js)
    module.exports = {
      // 配置文件
    }
    
    1.3.5 配置主题

    按照 配置主题 的要求,自定义主题需要用到类似 less-loader 提供的 less 变量覆盖功能:

    • 我们可以引入 craco-less 来帮助加载 less 样式和修改变量;

    1.安装 craco-less

    yarn add craco-less
    

    2.修改craco.config.js中的plugins:

    • 使用modifyVars可以在运行时修改LESS变量;
    const CracoLessPlugin = require('craco-less');
    
    module.exports = {
      plugins: [
        {
          plugin: CracoLessPlugin,
          options: {
            lessLoaderOptions: {
              lessOptions: {
                modifyVars: { '@primary-color': '#1DA57A' },
                javascriptEnabled: true,
              },
            },
          },
        },
      ],
    }
    

    3.引入antd的样式时,引入antd.less文件:

    // import 'antd/dist/antd.css'
    import 'antd/dist/antd.less';
    

    image-20210702220257434

    1.3.6 配置别名 @

    在项目开发中,某些组件或者文件的层级会较深,

    • 如果我们通过上层目录去引入就会出现这样的情况:../../../../components/button
    • 如果我们可以配置别名,就可以直接从根目录下面开始查找文件:@/components/button,甚至是:components/button

    配置别名也需要修改webpack的配置,当然我们也可以借助于 craco 来完成:

    const path = require('path'); // path模块是node.js中处理路径的核心模块
    const resolve = dir => path.resolve(__dirname,dir) // __dirname当前craco.congig.js的路径, dir指传传过来的路径, 进行拼接
    
    module.exports = {
    ...
    ,
      webpack: {
        alias: { // 别名
          '@': resolve("src"), // @代表: __dirname(当前文件的路径) + src  所以=>   @ 等价于 /scr
          'components':resolve("src/components")// components代表: 使用components 等价于 components/src
        }
      }
    }
    

    在导入时就可以按照下面的方式来使用了:

    import HYTitle from '../../title' // 原来的做法
    import HYTitle from '@/components/title' // 使用 @
    

    二十. React网络请求选择

    1.Fetch Api

    image-20210703102333675

    2.Axios的基本使用

    2.1 安装 axios

    npm i axios
    

    2.2 二次封装axios

    请求拦截加token , 响应拦截 判断后端返回的 响应码

    二十一. react-transition-group

    react-transition-group用来给一个组件的显示和消失添加某种过渡动画

    1.0. 介绍

    react-transition-group主要包含四个组件:

    • Transition

      • 该组件是一个和平台无关的组件(不一定要结合CSS);
      • 在前端开发中,我们一般是结合CSS来完成样式,所以比较常用的是CSSTransition;
    • CSSTransition

      • 在前端开发中,通常使用CSSTransition来完成过渡动画效果
    • SwitchTransition

      • 两个组件显示和隐藏切换时,使用该组件
    • TransitionGroup

      • 将多个动画组件包裹在其中,一般用于列表中元素的动画;

    2.0 CSSTransition使用

    2.1 CSSTransition是基于Transition组件构建的:
    • 自己通过 className="自定义一个类名" "chen-enter"// 就是显示时的class名字

    • CSSTransition执行过程中, 有三个状态: appear, enter, exit

      动画流程 页面首次加载触发的动画 显示时触发的动画 隐藏时触发的动画
      动画开始 -appear -enter exit
      动画执行 -appear-active -enter-active -exit-active
      动画结束 -appear-done -enter-done -exit-done
    • 然后自己根据这些类名写 需要的CSS动画样式

    2.2 CSSTransition常见对应的属性:
    • in:触发进入或者退出状态(显示和隐藏的时候才要in)

      • 如果添加了unmountOnExit={true},那么该组件会在执行退出动画结束后被移除掉;
      • 当in为true时,触发进入状态,会添加-enter、-enter-acitve的class开始执行动画,当动画执行结束后,会移除两个class,并且添加-enter-done的class;
      • 当in为false时,触发退出状态,会添加-exit、-exit-active的class开始执行动画,当动画执行结束后,会移除两个class,并且添加-enter-done的class;
    • classNames:动画class的名称

      • 决定了在编写css时,对应的class名称:比如card-enter、card-enter-active、card-enter-done;
    • timeout:

      • 过渡动画的时间
    • unmountOnExit:

      • unmountOnExit = {true}

      • 是否开始 隐藏DOM 整个节点都隐藏

    • appear:

      • 是否在初次进入添加动画(需要和in同时为true)需要配置 -appear, 所以最好的方法是 -appear 和 -enter 都设置成一个动画 .chen-appear , .chen-enter{}
    • 其他属性可以参考官网来学习:

    2.3. CSSTransition对应的钩子函数:

    主要为了检测动画的执行过程,来完成一些JavaScript的操作

    • onEnter:在进入动画之前被触发;

    • onEntering:在应用进入动画时被触发;

    • onEntered:在应用进入动画结束后被触发;

    • onExit:退出动画前

    • onExiting:退出动画;

    • onExited:退出动画后

      image-20210704140215381

    3. SwitchTransition

    3.1 SwitchTransition可以完成两个组件之间切换的炫酷动画:
    • 比如我们有一个按钮需要在on和off之间切换,我们希望看到on先从左侧退出,off再从右侧进入;
    • 这个动画在vue中被称之为 vue transition modes;
    • react-transition-group中使用SwitchTransition来实现该动画;
    3.2 SwitchTransition中主要有一个属性:mode,有两个值
    • in-out:表示新组件先进入,旧组件再移除;
    • out-in:表示就组件先移除,新组建再进入;
    3.3如何使用SwitchTransition呢?
    • SwitchTransition组件里面要有CSSTransition或者Transition组件,不能直接包裹你想要切换的组件;
    • SwitchTransition里面的CSSTransition或Transition组件不再像以前那样接受in属性来判断元素是何种状态,取而代之的是key属性;

    我们来演练一个按钮的入场和出场效果:

    image-20210704142927286

    4. TransitionGroup

    • 当一个React组件添加或者移除一个DOM的时候可以轻易的实现CSS动画
    • 你必须为CSSTransitionGroup的子级元素添加一个key属性,即使是渲染一个唯一的子元素的时候。React通过key来判断那个子级元素进入视野或者离开视野(显示或者隐藏)

    image-20210704150142463

    image-20210704150318893

    二十二. React中的纯函数

    • 确定的输入,一定会有产生确定的输出 (输出得到的结果,是确定中的值)
    • 函数在执行过程中,不能产生副作用

    1.0 纯函数需要满足一下几点

    • 相同输入总是会返回相同的输出。
    • 不产生副作用。
    • 不依赖于外部状态。
    例子1: 纯函数
    function sum(num1, num2){
          return num1 + num2
        }
        sum(20,30)
        sum(20,40)
    
    例子2: 不是纯函数
    /* 不是纯函数,在执行中产生副作用 */
        function print(info){
          info.name = "xxx" // 修改了值
          console.log(info.name)
        }
    

    image-20210704175149803

    二十三. Redux

    1.0 整体做法

    /* 整体写法 */
    
    // const redux =  require('redux')
    import redux from 'redux' // node.js V13.2才支持
    
    // 1.默认数据
    const initialState = { 
      counter: 0
    }
    
    // 2.创建一个 reducer纯函数
    function reducer(state = initialState, action) {
      switch (action.type) {
        case 'INCREMENT':
          return {...state,counter: state.counter + 1}
          case 'DECREMENT':
            return {...state,counter: state.counter - 1}
        default:
          state;
      }
    }
    
    // 3. store(创建的时候需要传入一个reducer)
    const store = redux.createStore(reducer)
    
    // 5.订阅store的修改
    store.subscribe(() => {
      console.log('counter', store.getState().counter)
    })
    
    
    // 4. 创建actions
    const action1 = {type: "INCREMENT"};
    const action2 = {type: "DECREMENT"};
    
    // 5.派发actions
    store.dispatch(action1)
    store.dispatch(action2)
    
    

    1.2 封装写法(开发模式)

    actionCreators.js 派发的函数
    /* 3. 创建actions派发的函数
     * 导入全局常量派发名字 constants.js
     * @Date: 2021-07-04 23:10:05
     */
    import {ADD_NUMBER,SUB_NUMBER} from "./constants.js"
    
    export function addAction(num) {
      return {
        type: ADD_NUMBER, // 4.这里派发的名字是由constants.js管理
        num
      }
    }
    
    export function subAction(num) {
      return {
        type: SUB_NUMBER, 
        num
      }
    }
    
    constants.js 放一些常量
    /* 4.在这里用常量保存 axion派发的名字(统一方便管理)
     * @Date: 2021-07-04 23:16:33
     */
    export const ADD_NUMBER = "ADD_NUMBER"
    export const SUB_NUMBER ="SUN_NUMBER"
    
    index.js主要文件移入 redux
    /* 1.做中间键(主要文件)
       2.引入redux包, 
       3.导入默认值state 和 纯函数(处理逻辑) reducer.js
     * @Date: 2021-07-04 22:48:29
     */
    import redux from 'redux'
    
    import reducer from './reducer.js'
    
    /* store(创建的时候需要传入一个reducer)纯函数 */
    const store = redux.createStore(reducer)
    
    export default store
    
    reducer.js 用来处理state的文件
    /* 2.创建一个默认值 和 纯函数(加需要处理的逻辑)
     * @Date: 2021-07-04 22:51:27
     */
    
    import { ADD_NUMBER, SUB_NUMBER} from './constants.js'
    
    // 创建一个默认值
    const defaultState = {
      counter: 0
    }
    
    // 创建一个 reducer纯函数
    function reducer(state = defaultState, action) {
      switch(action.type) {
        case ADD_NUMBER:
          return {...state, counter:state.counter + action.num}
        case SUB_NUMBER:
          return {...state, counter:state.counter - action.num}
        default:
        return state
      }
    }
    
    export default reducer
    
    index2.js 业务文件(需要使用redux的文件)
    /* 主要使用strore文件  
       1.导入store  index.js
       2.导入派发的函数 actionCreators.js
       3.订阅store的值
     * @Date: 2021-07-04 23:02:03
     */
    import store from './store/index.js'
    
    import {addAction, subAction} from './store/actionCreators.js'
    
    // 订阅store的修改
    store.subscribe(() => {
      console.log('counter', store.getState().counter)
    })
    
    // 派发函数
    store.dispatch(addAction(10))
    store.dispatch(subAction(8))
    

    正常操作: constants .js ===>actionCreators .js ===> reducer.js ===> 业务文件(需要调用redux)派发action

    1.3 Redux使用流程

    image-20210705202548553

    1.4 脚手架里使用Redux

    脚手架需要在 componentDidMount里 订阅 store.subscribe
    /*
     * @Date: 2021-07-05 21:01:27
     */
    import React, { PureComponent } from 'react'
    
    import store from '../store'
    
    import { addAction,subAction } from '../store/actionCreators'
    
    export default class Home extends PureComponent {
      constructor(prost) {
        super(prost)
        this.state = {
          counter: store.getState().counter
        }
      }
    
      componentDidMount() { // 订阅(只有订阅了 视图才会更新)
        this.unsubscribue = store.subscribe(() => {
          this.setState({
            counter: store.getState().counter
          })
        })
      }
    
      componentWillUnmount() { // 取消订阅
        this.unsubscribue()
      }
    
      render() {
        const { counter } = this.state
        return (
          <div>
            <span>home</span>
            <h2>当前计数: {counter}</h2>
            <button onClick={e => this.increment()}>+1</button>
            <button onClick={e => this.addNumber(5)}>+5</button>
          </div>
        )
      }
      increment(){
        store.dispatch(addAction())
      }
    
      addNumber(num){
        store.dispatch(subAction(num))
      }
    }
    
    

    1.5 react-redux 库使用

    本库并不是 Redux 内置,需要单独安装。因为一般会和 Redux 一起使用

    1.0 下载
    npm install --save react-redux
    
    1.1 使用
    import store from './store'
    import { connect, Provider } from 'react-redux'
     ReactDOM.render(
       <Provider store={store}> // 固定属性store
         <App4 />
       </Provider>
     ,document.getElementById('root'));
    
    1.3源码解析

    image-20210706140726913

    ReactReduxContext来自另外一个文件:

    image-20210706140917458

    connect函数最终调用的是connectHOC:

    image-20210706143354027

    1.6 redux 异步请求

    在组件中异步请求

    image-20210706162932483

    1.7 redux异步请求 + 中间件redux-thunk(重要)

    1.0 在redux中发送异步请求

    image-20210706163222661

    1.1 使用步骤
    • 1.先下载 redux-thunk中间件

      yarn add redux-thunk
      
    • 2.在store里引用中间件

    image-20210706172606546

    • 3.在组件里 调用一个派遣函数

    image-20210706190754605

    • 4 派遣一个action

    image-20210706190929029

    • 中转派遣

    image-20210706191316602

    两个参数 第一个派遣, 第二个可以拿到 store里面的数据

    image-20210801233319549

    1.8 redux-saga

    1.saga介绍

    image-20210708002441271

    image-20210708001533706

    • takeEvery:可以传入多个监听的actionType,每一个都可以被执行(对应有一个takeLastest,会取消前面的)
    • put:在saga中派发action不再是通过dispatch,而是通过put;
    • all:可以在yield的时候put多个action;
    2. 安装
    yarn add redux-saga
    
    3. 使用
    import { takeEvery, put, all } from 'redux-saga/effects';
    import axios from 'axios'
    import { FETCH_HOME_MULTIDATA } from './constants';
    import { bologAction } from './actionCreators'
    
      function* fetchHomeMultdata(action) { //
        const res = yield   axios({
          url: 'https://c1998.cn/apis/get/',
          params: {
            page: 1,
            per_page: 10
          }
        })
        const data = res.data.data
        yield put(bologAction(data)) // put 代替了 dispatch
      }
    
      function* mySaga() {
        // yield takeEvery(FETCH_HOME_MULTIDATA, fetchHomeMultdata)
    
        yield all([
          yield takeEvery(FETCH_HOME_MULTIDATA, fetchHomeMultdata)
        ]);
      }
    
    export default mySaga
    

    1.9 reducer代码拆分

    1.0 我们来看一下目前我们的reducer:
    • 当前这个reducer既有处理counter的代码,又有处理home页面的数据;
    • 后续counter相关的状态或home相关的状态会进一步变得更加复杂;
    • 我们也会继续添加其他的相关状态,比如购物车、分类、歌单等等;

    如果将所有的状态都放到一个reducer中进行管理,随着项目的日趋庞大,必然会造成代码臃肿、难以维护。

    我们先抽取一个对counter处理的reducer:

    // 1.我们先抽取一个对counter处理的reducer:
    const initialCounterState = { // 默认值
      counter: 0
    }
    function counterReducer(state = initialCounterState, action) {
      switch(action.type) {
        case ADD_NUMBER:
          return {...state, counter:state.counter + 1}
        case SUB_NUMBER:
          return {...state, counter:state.counter + action.num}
        case Mult_NUMBER:
          console.log('xxx')
          return {...state, counter:state.counter * action.num}
        default:
          return state;
      }
    }
    

    再抽取一个对home处理的reducer:

    // 再抽取一个对home处理的reducer:
    const initialHomeState = {
      bologList:[]
    }
    function homeReducer(state = initialHomeState, action) {
      switch(action.type) {
        case BOLOG_LISTS:
          console.log('执行了kk',action)
          return {...state, bologList: action.bologList}
        default:
        return state
      }
    }
    

    合并起来

    // 如果将它们合并起来呢?
    function reducer(state = {}, action) {
      return {
        counterInfo: counterReducer(state.counterInfo, action),
        homeInfo: homeReducer(state.homeInfo, action)
      }
    }
    

    使用

    image-20210708221413296

    image-20210708221514245

    1.1 合并reducers

    combineReducers: 合并 redux

    image-20210708232856108

    image-20210708232727661

    2.0 ImmutableJS

    2.1 React中的state如何管理

    image-20210708233007455

    React的作者自己的建议

    image-20210708233630880

    具体代码看 06-text-react pages8(终极版本)

    FAQ:

    单向数据流:

    image-20210708234654454

    二十四. Router路由原理

    1.1前端路由原理

    URL的hash
    <a href="#/home">home</a>
    <a href="#/about">about</a>
    <div class="router-view"></div>
    
    // 1.获取router-view
    const routerViewEl = document.querySelector(".router-view");
    
    // 2.监听hashchange,来显示对应页面的内容
    window.addEventListener("hashchange", () => {
    switch(location.hash) {
      case "#/home": 	
        routerViewEl.innerHTML = "home";
        break;
      case "#/about":
        routerViewEl.innerHTML = "about";
        break;
      default:
        routerViewEl.innerHTML = "default";
    }
    })
    
    • URL的hash也就是锚点(#), 本质上是改变window.location的href属性;
    • 我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新;
    • hash的优势就是兼容性更好,在老版IE中都可以运行,但是缺陷是有一个#,显得不像一个真实的路径。

    1.2 HTML5的History

      const routerViewEl = document.querySelector(".router-view");
    
      // 2.监听所有的a元素
      const aEls = document.getElementsByTagName("a");
      for (let aEl of aEls) {
        aEl.addEventListener("click", (e) => {
          e.preventDefault();
          const href = aEl.getAttribute("href");
          console.log(href);
          history.pushState({}, "", href);
          historyChange();
        })
      }
      
       // 3.监听popstate和go操作
      window.addEventListener("popstate", historyChange);
      window.addEventListener("go", historyChange);
      
       // 4.执行设置页面操作, 来显示对应的页面内容
      function historyChange() {
        switch(location.pathname) {
          case "/home":
            routerViewEl.innerHTML = "home";
            break;
          case "/about":
            routerViewEl.innerHTML = "about";
            break;
          default:
            routerViewEl.innerHTML = "default";
        }
      }
    

    history接口是HTML5新增的, 它有l六种模式改变URL而不刷新页面:

    • replaceState:替换原来的路径;
    • pushState:使用新的路径;
    • popState:路径的回退;
    • go:向前或向后改变路径;
    • forword:向前改变路径;
    • back:向后改变路径;

    二十五. react-router

    yran add react-router-dom
    

    1.react-router最主要的API是给我们提供的一些组件:

    • BrowserRouter或HashRouter

      • Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件;
      • BrowserRouter使用history模式;
      • HashRouter使用hash模式;
    • Link和NavLink:

      • 通常路径的跳转是使用Link组件,最终会被渲染成a元素;
      • NavLink是在Link基础之上增加了一些样式属性(后续学习);
      • to属性:Link中最重要的属性,用于设置跳转到的路径;
    • Route:

      • Route用于路径的匹配;
      • path属性:用于设置匹配到的路径;
      • component属性:设置匹配到路径后,渲染的组件;
      • exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件;

    在App中进行如下演练:

    import React, { PureComponent } from 'react'
    import {BrowserRouter, Route, Link} from 'react-router-dom'
    import Home from './Home'
    import About from './About'
    import Profile from './Profile'
    
    export default class App extends PureComponent {
      render() {
        return (
          <div>
            <BrowserRouter>
            <Link to="/">首页</Link>
            <Link to="about">关于</Link>
            <Link to="/profile">我的</Link>
    
            <Route exact path="/" component={Home}></Route>
            <Route path="/about" component={About}/>
            <Route path="/profile" component={Profile}/>
            </BrowserRouter>
          </div>
        )
      }
    }
    

    2. NavLink的使用

    路径选中时,对应的a元素变为红色

    这个时候,我们要使用NavLink组件来替代Link组件:

    • activeStyle:活跃时(匹配时)的样式;
    • activeClassName:活跃时添加的class;
    • exact:是否精准匹配;

    先演示activeStyle:

    {/* 自定义router选中高亮类名 */}
    <NavLink exact to="/" activeClassName="link-active">首页</NavLink>
    <NavLink to="/about" activeClassName="link-active">关于</NavLink>
    <NavLink to="/profile" activeClassName="link-active">我的</NavLink>
    

    3. Switch的作用

    1.我们来看下面的路由规则:
    • 当我们匹配到某一个路径时,我们会发现有一些问题;
    • 比如/about路径匹配到的同时,/:userid也被匹配到了,并且最后的一个NoMatch组件总是被匹配到;

    原因是什么呢?默认情况下,react-router中只要是路径被匹配到的Route对应的组件都会被渲染;

    2.但是实际开发中,我们往往希望有一种排他的思想:
    • 只要匹配到了第一个,那么后面的就不应该继续匹配了;
    • 这个时候我们可以使用Switch来将所有的Route进行包裹即可;

    image-20210709224111419

    4. Redirect(重定向)

    Redirect用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中:

    我们这里使用这个的一个案例:

    • 用户跳转到User界面;

    • 但是在User界面有一个isLogin用于记录用户是否登录:

      • true:那么显示用户的名称;
      • false:直接重定向到登录界面;

    App.js中提前定义好Login页面对应的Route:

    export default class uesr extends PureComponent {
      constructor(props){
        super(props)
        this.state = {
          isLogin: true
        }
      }
      render() {
        return this.state.isLogin ? (
          <div>
            <h2>user</h2>
            <h2>用户名:__</h2>
          </div>
        ):<Redirect to="/login"/> // 重定向
      }
    }
    

    5.路由嵌套

    这里我们假设about页面中有两个页面内容:

    • 商品列表和消息列表;
    • 点击不同的链接可以跳转到不同的地方,显示不同的内容;
    export default class About extends PureComponent {
      render() {
        return (
          <div>
            <NavLink exact to="/about" activeClassName="link-active">电器</NavLink>
            <NavLink exact to="/about/culture" activeClassName="link-active">衣服</NavLink>
            <NavLink exact to="/about/contact" activeClassName="link-active">食品</NavLink>
    
            <Switch>
              <Route exact path="/about" component={AboutProduct}></Route>
              <Route path="/about/culture" component={AboutMessagea}></Route>
              <Route path="/about/contact" component={AboutMessageb}></Route>
            </Switch>
          </div>
        )
      }
    }
    

    image-20210709232233814

    6.手动跳转路由

    目前我们实现的跳转主要是通过Link或者NavLink进行跳转的,实际上我们也可以通过JavaScript代码进行跳转。

    但是通过JavaScript代码进行跳转有一个前提:必须获取到history对象。

    如何可以获取到history的对象呢?两种方式

    • 方式一:如果该组件是通过路由直接跳转过来的,那么可以直接获取history、location、match对象
    • 方式二:如果该组件是一个普通渲染的组件,那么不可以直接获取history、location、match对象;

    那么如果普通的组件也希望获取对应的对象属性应该怎么做呢?

    • 前面我们学习过高阶组件,可以在组件中添加想要的属性;
    • react-router也是通过高阶组件为我们的组件添加相关的属性的;

    如果我们希望在App组件中获取到history对象,必须满足以下两个条件:

    • App组件必须包裹在Router组件之内;
    • App组件使用withRouter高阶组件包裹;

    index.js代码修改如下:

    该组件是通过路由直接跳转过来的: 可以直接跳转

    <button onClick={e => this.pushToProfile()}>前往我的</button>
      pushToProfile(){
        this.props.history.push("/user")
      }
    

    该组件是一个普通渲染的组件: 不能直接跳转会报错

    image-20210709234503939

    解决:

    使用 withRouter()高阶函数 导出的组件可以直接拿到 history

    import { Route, Switch, NavLink, withRouter } from 'react-router-dom';
    ...省略其他的导入代码
    
    class App extends PureComponent {
      render() {
        console.log(this.props.history);
    
        return (
          <div>
            ...其他代码
            <button onClick={e => this.pushToProfile()}>我的</button>
    
            <Switch>
              <Route exact path="/" component={Home} />
              <Route path="/about" component={About} />
              <Route path="/profile" component={Profile} />
              <Route path="/:userid" component={User} />
              <Route component={NoMatch} />
            </Switch>
          </div>
        )
      }
    
      pushToProfile() {
        this.props.history.push("/profile");
      }
    }
    
    export default withRouter(App); // 使用 withRouter()导出路由
    

    image-20210709235010393

    7.动态路由(传递参数)

    传递参数有三种方式:

    • 动态路由的方式;
    • search传递参数;
    • to传入对象;
    1. 第一种: 动态路由传递 (拼接传递)
    • 我们可以直接通过match对象中获取id;
    • 这里我们没有使用withRouter,原因是因为Detail本身就是通过路由进行的跳转;
    // 1.传递 app.js
    <NavLink to={`/detail/${id}`} activeClassName="link-active">动态路由</NavLink>
    
    <Route path="/detail/:id" component={Detail}/>
    
    // 2. 接受参数detail.js
    export default class Detail extends PureComponent {
      render() {
        const match = this.props.match; // 接受参数
        console.log(match)
        return (
          <div>
            <h2>Detail: {this.props.match.params.id}</h2>
          </div>
        )
      }
    }
    
    2. search传递参数 (query传递 ) /?name=whty&, 已经不推荐了
    • 我们在跳转的路径中添加了一些query参数;
    1.传递 app.js
    <NavLink to="/detail2?name=why&age=18" activeClassName="link-active">search(query)传递</NavLink>
    
    <Route path="/detail2" component={Detail2}/>
    
    
    export default class Detail extends PureComponent {
      render() {
        const match = this.props.location.search; // 接受参数
        console.log(this.props.location.search)
        return (
          <div>
            <h2>detail2: {match}</h2>
          </div>
        )
      }
    }
    

    image-20210710132713134

    3.to传递参数

    to可以直接传入一个对象

     const info = {name:'chen',age:'23', gander: '男'}
     // app.js
    <NavLink to={{
      pathname:'/detail3',
      search:"?name=abc",
      state:info
    }} activeClassName="link-active">to(params)传递</NavLink>
    
    export default class Detail extends PureComponent {
    
      render() {
        const match = this.props.location.state; // 接受参数
        console.log(this.props.location.state)
        return (
          <div>
            <h2>detail3: {match.name}</h2>
          </div>
        )
      }
    }
    

    image-20210710132848171

    8.react-router-config 路由统一配置 (类似vue路由配置文件)***

    目前我们所有的路由定义都是直接使用Route组件,并且添加属性来完成的。

    但是这样的方式会让路由变得非常混乱,我们希望将所有的路由配置放到一个地方进行集中管理:

    • 这个时候可以使用react-router-config来完成;
    1.安装react-router-config:
    yarn add react-router-config
    
    2.创建router/index.js文件:
    import Home from '../pages/Home'
    import About,{AboutProduct, AboutMessageb, AboutMessagea} from '../pages/About'
    import Profile from '../pages/Profile'
    
    const routes = [
      {
        path:'/',
        exact: true,
        component: Home
      },
      {
        path:'/about',
        component: About,
        routes:[ // 子路由
          {
            path:'/about',
            exact: true,
            component: AboutProduct
          },
          {
            path:'/about/culture',
            component: AboutMessagea
          },
          {
            path:'/about/contact',
            component: AboutMessageb
          }
        ]
      },
      {
        path:'/profile',
        component: Profile
      },
    
    ]
    
    export default routes
    
    3. 在app配置路由: 使用路由占位符
    import router from './router'
    import  { renderRoutes } from 'react-router-config'
    
    {renderRoutes(router)}  // 类似路由占位符
    
    4. 如果是子组件: 使用路由占位符
    • 在跳转到的路由组件中会多一个 this.props.route 属性;
    • route属性代表当前跳转到的路由对象,可以通过该属性获取到 routes
    import  { renderRoutes } from 'react-router-config'
    
    {renderRoutes(this.props.route.routes)}
    

    9.路由懒加载(优化)

    好处: 只有等当前路由使用了, 才去加载当前的页面或组件
    1.router.js配置以下代码:
    - import CLDdiscover from "@/pages/discover"
    + const CLDdiscover = React.lazy(_ => import("../pages/discover"));
    
    2.APP.js配置以下代码:

    image-20210803160201336

    3.通过懒加载打包, 里面的js会 分开打

    image-20210803160805950

    二十六. React Hooks

    1. 简单总结一下hooks:

    • 它可以让我们在不编写class的情况下使用state以及其他的React特性;
    • 但是我们可以由此延伸出非常多的用法,来让我们前面所提到的问题得到解决;

    Hook的使用场景:

    • Hook的出现基本可以代替我们之前所有使用class组件的地方(除了一些非常不常用的场景);
    • 但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它;
    • Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用;
    export default function CounterHook() {
     
       // 写法一
      // const arr = useState(0)
      // const state = arr[0]
      // const setState = arr[1]
    
      // 写法二
      const [count, setCount] = useState(0)
    
      return (
        <div>
          <h2>当前计数: {count}</h2>
          <button onClick={e => setCount(count + 1)}>+!</button>
        </div>
      )
    }
    

    2. useState解析

    image-20210712211202646

    HOOK指的是useState,useEffect这样的函数

    Hooks是对这类函数的统称

    3.认识useState

    image-20210712211745621

    4.useEffect (代替生命周期)

    类似vue里面的 created()

    1.0 目前我们已经通过hook在函数式组件中定义state,那么类似于生命周期这些呢?
    • Effect Hook 可以让你来完成一些类似于class中生命周期的功能;
    • 事实上,类似于网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(Side Effects);
    • 所以对于完成这些功能的Hook被称之为 Effect Hook;
    2.0 useEffect简介

    useEffect,字面意思可以理解为"执行副作用操作",对比于以前react class的写法,可以把 useEffect看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

    3.0 使用方法

    每次都触发

    // 相当于 componentDidMount 和 componentDidUpdate
    useEffect(() => {
      console.log(`You clicked ${count} times`);
    });
    

    只需要触发一次

     // 由于第二个参数传的是空数组,所以这里相当于componentDidMount
     useEffect(() => {
        console.log('订阅事件')
      },[])
    

    当属性值发生变化才触发

    export default function HookUseEffect() {
      const [count, setCount] = useState(0);
    
     // 只有当count发生变化的时候才会触发,show发生变化不会触发
      useEffect(() => {
        console.log('修改Dom', count)
      },[count])
    
      return (
        <div>
          <h2>{count}</h2>
          <button onClick={e => setCount(count + 1)}>+1</button>
        </div>
      )
    }
    
    触发, 然后销毁
    import React, { useEffect, useState } from 'react'
    
    export default function CustomScorllHook() {
      const [count, setCount] = useState(0);
      useEffect(() => {  // 触发
        setCount(10)
        return () => { // 销毁
          setCount(0) 
        }
      })
    
      return (
        <div>
          <h2 style={{padding: "1000px 0"}}>当前的位置: {count}</h2>
        </div>
      )
    }
    
    
    useState默认值如果需要计算 ,可以写成一个回调函数
    const [name, setName] = useState(() =>{
    return JSON.stringify(window.localStorage.getItem('name'))
    })
    

    5.useContext(跨组件通信)

    在之前的开发中,我们要在组件中使用共享的Context有两种方式:

    • 类组件可以通过 类名.contextType = MyContext方式,在类中获取context;
    • 多个Context或者在函数式组件中通过 MyContext.Consumer 方式共享context;

    但是多个Context共享时的方式会存在大量的嵌套:

    • Context Hook允许我们通过Hook来直接获取某个Context的值(不需要嵌套);
    App.js
    import React, { PureComponent } from 'react'
    
    import ContextHookDemo2 from './04_useContext的使用/02.useContext写法'
     
    export const UserContext = React.createContext();
    export const ThemContext = React.createContext();
    
    export default class App extends PureComponent {
      render() {
        return (
          <div>
             <UserContext.Provider value={{name:'chen',age:18}}>
               <ThemContext.Provider>
                 {/* <ContextHookDemo></ContextHookDemo>自己的组件 */}
                 <ContextHookDemo2/>
               </ThemContext.Provider>
             </UserContext.Provider>
          </div>
        )
      }
    }
    
    

    在对应的函数式组件中使用Context Hook:

    import React,{useContext} from 'react'
    import {UserContext,ThemContext } from '../App'
    
    export default function App() {
      const user = useContext(UserContext)
      return (
        <div>
          <span>{user.name}</span>
        </div>
      )
    }
    

    6.useRedux

    import React,{useState, useReducer} from 'react'
    
    import reducer from './reducer'
    
    export default function useReduxProfile() {
    
      const [state, dispatch] = useReducer(reducer, {counter: 0})
    
      return (
        <div>
            <h2>当前计数: {state.counter}</h2>
            <button onClick={e => {dispatch({type: "increment"})}}>+1</button>
            <button onClick={e => {dispatch({type: "decrement"})}}>-1</button>
        </div>
      )
    }
    

    很多人看到useReducer的第一反应应该是redux的某个替代品,其实并不是。

    useReducer仅仅是useState的一种替代方案:

    • 在某些场景下,如果state的处理逻辑比较复杂,我们可以通过useReducer来对其进行拆分;
    • 或者这次修改的state需要依赖之前的state时,也可以使用;

    单独创建一个reducer/counter.js文件:

    数据是不会共享的,它们只是使用了相同的counterReducer的函数而已。

    所以,useReducer只是useState的一种替代品,并不能替代Redux。

    7.useCallback性能优化

    子组件依赖的函数没有被改变,就不执行(组件的回调函数常用)

    1.0 useCallback实际的目的是为了进行性能的优化.

    如何进行性能的优化呢?

    • useCallback会返回一个函数的 memoized(记忆的) 值;
    • 在依赖不变的情况下,多次定义的时候,返回的值是相同的;
    2.0 useMemo 和 useCallback 接收的参数都是一样,第一个参数为回调 第二个参数为要依赖的数据

    在将一个组件中的函数,传递给子元素进行回调函数使用时, 使用useCallback对函数镜像处理

    image-20210714230059285

    8.useMemo性能优化

    依赖的函数没有发生变化就不会执行

    • useCallbackuseMemo的语法糖

    例子1:

    image-20210714233235136

    例子2:

    image-20210714234532518

    9.useCallback 和 useMemo的区别

    1.0 共同作用:

    仅仅 依赖数据 发生变化, 才会重新计算结果,也就是起到缓存的作用。

    2.0 两者区别:
    • useMemo计算结果是 return回来的值, 主要用于 缓存计算结果的值 ,应用场景如: 需要 计算的状态

    • useCallback 计算结果是 函数, 主要用于 缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。

    image-20210714235336948

    10. useRef

    useRef返回一个ref对象,返回的ref对象在组件的整个生命周期保持不变。

    最常用的ref是两种用法:

    • 用法一:引入DOM(或者组件,但是需要是class组件)元素;
    • 用法二:保存一个数据,这个对象在整个生命周期中可以保存不变;
    1. 0 用法一: 引用dom
    import React,{useRef,PureComponent} from 'react'
    
    class Son extends PureComponent{ 
      render(){
        return <h2>SON</h2>
      }
    }
    
    export default function useDomHook1() {
    
      const titleRef = useRef()
      const inputRef = useRef()
      const SonRef = useRef()
    
      function changeDom() {
        titleRef.current.innerHTML = "Hello world"
        inputRef.current.focus()
        console.log(SonRef.current,'xxx') // 打印组件的内容 只支持class组件
      }
    
      return (
        <div>
          <h2 ref={titleRef}>RefHookDome1</h2>
          <input type="text" ref={inputRef}/>
          <button onClick={ changeDom}>DOM替换title</button>
          <Son ref={SonRef}/>
        </div>
      )
    }
    
    
    1.1 用法二:使用ref保存上一次的某一个值
    • useRef可以想象成在ref对象中保存了一个.current的可变盒子;
    • useRef在组件重新渲染时,返回的依然是之前的ref对象,但是current是可以修改的;
    export default function RefHooksDome2() {
      const numRef = useRef(10) // red可以默认传入一个值 
      const [count, setCount] = useState(0)
    
      // 利用useEffect的效果 , 只有count改变了 才执行 numRef.current赋值(记录上一次的数据)
       useEffect(() => { 
         numRef.current = count
       },[count])
    
      function addInter() {
        setCount(count + 10)
     }
    
      return (
        <div>
          <h2>RefHooksDome2: {numRef.current}</h2>
          <h2>计算: {count}</h2>
          <button onClick={addInter}>+10</button>
        </div>
      )
    }
    

    11.forwardRef 和useImperativeHandle

    1.0 forwardRef
    • forwardRef可以用 ref 操作函数组件的所有dom方法
    import React, {useRef, forwardRef } from 'react'
    
      const HyInput = forwardRef((props, ref) =>{
        return <input ref={ref} type="text" />
      })
    
    export default function ForwardRedDome1() {
    
      const inputRef = useRef()
    
      return (
        <div>
          <HyInput ref={inputRef}/>
         <button onClick={e => inputRef.current.focus()}>操作function组件</button>
        </div>
      )
    }
    
    1.2 useImperativeHandle

    useImperativeHandle也是用来操作函数组件的

    forwardRef 和 useImperativeHandle区别

    • forwardRed是将子组件的DOM直接暴露给了父组件
    • useImperativeHandle可以只暴露固定的操作:
      • 比如只允许父组件可以操作focus,其他并不希望它随意操作
      • 相当于子组件暴露一个你只能修改color, 父组件通过ref只能修改color
    import React, { useRef, forwardRef, useImperativeHandle } from 'react'
    
    const HyInput = forwardRef((props, ref) => {
      const inputRef = useRef()
    
      useImperativeHandle(ref, () => ({
        focus: () => {
          inputRef.current.focus()
        }
      }))
      return <input ref={inputRef} type="text" />
    })
    
    export default function ForwardRedDome2() {
    
      const inputRef = useRef()
    
      return (
        <div>
          <HyInput ref={inputRef} />
          <button onClick={e => inputRef.current.focus()}>操作function组件</button>
        </div>
      )
    }
    

    12. useLayoutEffect

    useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已:

    • useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新;
    • useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新;

    如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect。

    例子1:

    通过 点击更新 count, 此时执行一次dom更新, useEffect监听到count发生变化也会执行内部的setCount来修改DOM, 从而导致两次刷新两次dom操作,出现白屏

    useLayoutEffect,可以代替useEffect, 等所有代码执行完毕后, 再进行DOM操作

    import React, { useEffect, useLayoutEffect, useState } from 'react'
    
    export default function UseLayoutEffectDom() {
    
      const [count, setCount] = useState(10)
      
      // 点击修改 count为:0 后会去更新dom, useEffect里面监听到count发生改变,也会更新dom 从而导致闪屏
      // useEffect(() => { 
      //   if(count == 0){
      //     setCount(Math.random()*50)
      //   }
      // },[count])
    
      // 使用 useLayoutEffect就不会出现闪屏, useLayoutEffect会等所有代码执行完毕后,再进行DOM的更新;
      useLayoutEffect(() => {
        if(count == 0){
          setCount(Math.random()*50)
        }
      },[count])
    
      return (
        <div>
          <h2>计数: {count}</h2>
          <button onClick={e => {setCount(0)}}>随机数</button>
        </div>
      )
    }
    

    14. 自定义hooks

    1.0练习一: Context的共享
    // app.js 创建需要共享的值
    <UserContext.Provider value={{name:'chen'}}>
    <ThemContext.Provider value={{value:'777'}}>
     <UseHooks/>
    </ThemContext.Provider>
    </UserContext.Provider>
    
    // userHooks.js 创建自定义hook 封装Context 
    import { useContext } from 'react'
    import { UserContext, ThemContext} from '../App'
    
     function useUserContext() {
      const user = useContext(UserContext)
      const token = useContext(ThemContext)
      
      return [user, token] // 自定义hook返回两个值
     }
    
     export default useUserContext
     
     // cont.js 需要使用的共享数据的组件
     import React from 'react'
    import useUserContext from '../hooks/user-hooks' // 引入自定义的hooks
    
    export default function UserHooks() {
    const [user, token] = useUserContext() // 直接获取自定义hook的函数
      return (
        <div>
          <h2>useHooks</h2>
          <h4>{user.name}</h4>
          <h4>{token.value}</h4>
        </div>
      )
    }
    
    1.1 练习二: 获取滚动距离
    // use-scroll.js  创建自定义hooks
    import { useEffect, useState} from "react";
    
    function useScrollHook() {
      const [scollNumber, setScollNumber] = useState(0)
    
      const getScorll = () => {
        setScollNumber(window.scrollY)
      }
    
      useEffect(() =>{
        
        document.addEventListener('scroll',getScorll)
        return () => {
          document.removeEventListener('scroll',getScorll)
        }
      },[])
      return scollNumber
    }
    
    export default useScrollHook
    
    // 使用自定义hooks
    import React, { useEffect, useState } from 'react'
    import useScrollHook from '../hooks/user-scroll-hook' // 导入自定义hooks
    
    export default function CustomScorllHook() {
    
      const scrollTop = useScrollHook()
      console.log(scrollTop)
    
        return (
        <div style={{padding: "1000px 0"}}>
          <h2 style={{position: "fixed", left:0 , top:0}}>当前的位置:{scrollTop}</h2>
        </div>
        )
        }
    
    
    1.2 练习三: 动态设置缓存
    // user-store-hook.js 创建自动以hooks
    import { useEffect, useState } from "react";
    
    function useLocalStorage(key) { // 传入缓存名字
      const [name, setName] = useState(() => {
        return JSON.parse(window.localStorage.getItem('name'))
      })
    
      useEffect(() => {
        window.localStorage.setItem(key, JSON.stringify(name))
      },[name])
    
      return [name,setName] // 导出setName是为了好调用这个方法
    }
    
    export default useLocalStorage
    
    // 使用自定义hooks
    import useLocalStorage  from '../hooks/user-store-hook' // 导入自定义hooks
    
    export default function CustomDataLocal() {
    
      // 使用自定义hook封装
    
      const [name, setName] = useLocalStorage('name')
    
      return (
        <div>
           <h2>CustomDataStoreHook: {name}</h2>
          <button onClick={e => setName("大哥666")}>设置</button>
        </div>
      )
    }
    

    二十七: 实战项目使用 redux

    1.先下载依赖包

    yarn add redux react-redux redux-thunk
    

    2.配置目录:

    image-20210722085209287

    3.先配置 最大的store

    image-20210722085550904

    4.配置一个: 合并所有子reudex 的 reducer文件

    导入子redux: 这里是配置好了一个 bander , 所以先引用了:

    image-20210722085849765

    5.通过Provider共享所有 store

    image-20210722144820503

    6. 配置需要的子redux

    • 创建一个bander文件夹 ,并且在文件夹内创建以下四个文件

    • 创建一个 Module模块, 里面放左右子 redux; 方便好找

    • 子redux 暴露出一个 reducer , 最外层的 reducer 利用 combineTeduers 对此进行合并

    image-20210722091720382

    7.配置子redux

    有两种写法:
    第一种: 是通过connect()
    • mapStateToProps:更新props————>作为输入源。返回一个对象,key为UI界面对应的名称,value为state处理的结果
    • mapDispatchToProps:更新action————>作为输出源。触发action更新reducer,进而更新state, 引起UI的变化
    • 缺点: 每次都需要写 mapStateToProps 和 mapDispatchToProps
    第二种: 通过useSelector()
    • useSelector可以订阅store, 当action被dispatched的时候,会运行selector。可以直接拿到stort
    • 优点: 代码简单明了,
    • 缺点: useSelector 使用的是 === 来比较, 比较的是两个引用类型 , 所以每次都会没 re-render重新加载(浪费性能)
    • 解决: 可以引用一个 shallowEqual(), 让 useSelector进行浅层比较;
    整体流程图:

    image-20210722164818843

    1.对应的代码流程图(connect写法):

    image-20210722170546546

    2. 对应的代码流程图(hooks写法) 常用!!!!

    useSelector()有个bug: 比较的是两个引用地址, 所以 需要 加上 shallowEqual

    shallowEqual: 是进行浅层比较(比较的是值)

    但是下图没有写: 需要自己加上

    image-20210722175333522

    8.解决 hooks里使用 useSelector() 的方法

    • useSelector()将对前一个选择器结果值和当前结果值进行引用比较。 如果它们不同,则将强制重新渲染组件如果它们相同,则组件将不会重新渲染
    1.例子:
    • 当我在 home组件里 更改redux里面的值 , about组件被重新渲染了(about组件没有依赖home更改的值)

      image-20210722213415227

      about组件被重新加载了(这是非常浪费性能的)

    image-20210722212644937

    2.原因?

    因为useSelector是 === 比较两个引用类型, 所以每次都会被重新

    image-20210722212614941

    3.解决办法: shallowEqual()

    ​ shallowEqual比较值相等,或者对象含有相同的属性、且属性值相等。(浅层比较)

    image-20210722214003262

    源码: 因为 shallowEqual 是浅层比较 比较的是 值相等

    image-20210722213646645

    二十八. 数据的可变引起的问题 (优化)

    1.介绍 Immutable

    Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。

    Map() : 是浅层比较只会比较一层对象,

    fromJS(): 是深层比较

    image-20210725211249536

    immutable.js

    image-20210725211259258

    2.为什么要在redux里使用

    • 因为在存函数里操作这个默认值,必须要进行浅拷贝一下(然后进行替换赋值)0

    • 但是如果数据量很大, 并且全部进行拷贝那就很影响性能了

    image-20210725115757968

    3.使用 Immutable

    1. 下载
    yarn add immutable
    
    2.在redux.js 用Map包裹默认值, 并且设置一个key

    image-20210725122347018

    3.使用 get() 拿刚刚设置的key

    image-20210725122456161

    4.最后的优化 redux-immutable

    Immutable 可以给应用带来极大的性能提升

    1.下载
    yarn add redux-immutable
    
    2.使用

    在最外层的 redux里使用

    • 在 最外层的 reducer.js修改一下配置

    image-20210725211700160

    • 使用

    image-20210725213717447

  • 相关阅读:
    基于Flume做FTP文件实时同步的windows服务。
    Java代码解决ElasticSearch的Result window is too large问题
    springboot+zuul(二)------智能负载
    springboot+zuul(一)------实现自定义过滤器、动态路由、动态负载。
    Docker 快速安装&搭建 Ngnix 环境,并配置反向代理
    如何通过 Freemark 优雅地生成那些花里胡哨的复杂样式 Excel 文件?
    Elasticserach 同步索引报错:ElasticSearch ClusterBlockException[blocked by: [FORBIDDEN/12/index read-only / allow delete (api)]
    如何通过Gitalk评论插件,5分钟为你的博客快速集成评论功能
    一文教您如何通过 Docker 快速搭建各种测试环境(Mysql, Redis, Elasticsearch, MongoDB) | 建议收藏
    Docker 快速安装&搭建 Mysql 环境
  • 原文地址:https://www.cnblogs.com/cl1998/p/15172125.html
Copyright © 2020-2023  润新知