zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    POJ 3126 Prime Path
    POJ 2429 GCD & LCM Inverse
    POJ 2395 Out of Hay
    【Codeforces 105D】 Bag of mice
    【POJ 3071】 Football
    【POJ 2096】 Collecting Bugs
    【CQOI 2009】 余数之和
    【Codeforces 258E】 Devu and Flowers
    【SDOI 2010】 古代猪文
    【BZOJ 2982】 combination
  • 原文地址:https://www.cnblogs.com/cl1998/p/15172125.html
Copyright © 2011-2022 走看看