美文网首页
react 学习笔记一

react 学习笔记一

作者: 人话博客 | 来源:发表于2022-01-19 07:08 被阅读0次

    1. setState

    setState 的两种写法

    (1). setState(stateChange,[callback]) ---- 对象式的setState
        1. stateChange 为状态改变的对象 `{}`
        2. callback 是可选参数,会在状态改变以及render函数调用完毕之后执行.
    
    (2). setState(updater,[callback]) ---- 函数式的setState
        1. updater 为返回 stateChange 的函数 .
        2. updater 可以接受两参数 prevState , props
        3. callback 是可选参数,会在状态改变以及render函数调用完毕之后执行.
        
    总结: 
        1. 对象式setState就是函数式的语法糖.(在内部会定义一个函数,返回stateChange对象)
        2. 使用原则:
            1. 如果修改的状态不依赖原来的状态,使用对象式setState
            2. 如果修改的状态依赖于原状态,使用函数式setState
            3. 如果需要在setState之后,拿到最新的状态,则需要使用可选参数 callback.
    

    代码例子:

    import React, { Component } from 'react'
    
    export default class Demo extends Component {
      state = {
        count: 1
      }
    
      add = () => {
        //#region 对象式的setState
        // 在原来的对象上修改,也可以修改状态
        // this.state.count++
        // this.setState(this.state)
    
        // 第一种setState的方法
        // this.setState({ count: this.state.count + 1 })
        // console.log(this.state.count) // 1 setState 修改也是异步的,所以这里返回的是1
        // 如果你需要使用最新的state状态,在setState的第二个回调函数里就可以拿到(在state和界面更新之后调用)
        // this.setState({ count: this.state.count + 1 }, () => {
        //   console.log(this.state.count) // 2
        // })
        // this.setState({ count: this.state.count + 1 })
        // setTimeout(() => {
        //   console.log(this.state.count);
        // }, 0);
        //#endregion
    
        //#region 函数式的setState
        console.log('setState');
        this.setState((prevState, props) => {
          console.log(props);
          return { count: prevState.count + 1 }
        }, () => {
          // 在状态更新之后执行render之后执行
          console.log('callback');
          console.log(this.state.count)
        })
        //#endregion
      }
      render() {
        console.log('render');
        const { count } = this.state
        return (
          <div>
            <h2>当前计数为: {count}</h2>
            <button onClick={this.add}>点我+1</button>
          </div>
        )
      }
    }
    
    
    

    2. 路由组件的 lazyLoad

    路由组件的lazyLoad

    // 1. 通过React的lazy函数配合 import()函数动态加载路由组件 ====> 路由组件的代码会单独打包
    import { lazy } from 'react'
    const Login = lazy(()=>import('@/pages/login'))
    
    // 2. 通过 <Suspense> 指定在加到得到的路由打包组件加载完毕之后,自由的显示一个Loading组件
    render () {
        return (
            <Suspense fallback={<Loadding />}>
                <Switch>
                    <Route path="xxx/xxx" component={xxxComponent} />
                </Switch>
            </Suspense>
        )
    }
    

    3. Hooks

    React Hook是什么?

    1. Hook 是 react 16.8.0 版本中新增加的新特性/新语法.
    2. 可以让你在函数式组件中,使用class组件的其他特性(比如,state,生命周期等)

    三个常用的hook

    1. React.useState()
    2. React.useEffect()
    3. React.useRef()

    React.useState

    1. React.useState 可以让函数式组件,也可以用于类似于class组件的 state 状态.
    2. 语法 const [xxx,setXXX] = React.useState(initValue)
    3. React.useState参数和返回值说明
      1. 参数 initValue 就是当前状态的初始值.
      2. 返回值:
        1. 返回值是一个数组 [xxx,setXXX]
        2. 第一个xxx,就是state的值.
        3. 第二个参数是用于修改xxx为新状态的函数.
    4. setValue的两种用户
      1. setXXX(newVal):参数为非函数,直接用新的状态覆盖原来旧的状态.
      2. setXXX(prevState=>newState): 参数为函数,函数接受上一次的状态,在此函数内部返回一个新的状态覆盖原来旧的状态.

    演示代码:

    import React, { useState } from 'react';
    
    
    function Demo(props) {
      // 让函数式组件可以拥有自己的状态,第一次调用此函数组件是,react内部会将count缓存,后期再次调用此组件时,用的是缓存的值.
      const [count, setCount] = useState(0)
      const [name, setName] = useState('jack')
    
      // 点我加1
      const add = () => {
        // setCount(count + 1)
        setCount((count) => count + 1)
        // console.log('newCount:', count); // 同步无法拿到最新的count
      }
    
      const change = () => {
        setName(name => name += "gqs")
      }
    
      return (
        <div>
          <h2>当前求和为:{count}</h2>
          <h2>当前的名字:{name}</h2>
          <button onClick={add}>点我加1</button>
          <button onClick={change}>点我改名</button>
        </div>
      );
    }
    
    export default Demo;
    

    React.useEffect

    1. React.useEffect 可以让你在函数式组件中,模拟class组件的生命周期钩子函数.(所谓的副作用操作)
    2. React中有哪些副作用操作?
      1. 异步请求
      2. 设置发布订阅/启动定时器
      3. 手动更改真实的DOM.
    3. 语法和说明
      // 等价于 class 组件的 componentDidUpdate 钩子
      useEffect(()=>{
          console.log('当任意state发生改变时,都会触发-componentDidUpdate') // 监听所有的属性发生改变时,都会出发此函数,等价于 componentDidUpdate
      })
      
      // 等价于 class 组件的 componentDidMount 钩子
      useEffect(()=>{
          console.log('componentDidMount') 
      },[]) // 参数传空数组,不监听任何state的change,等价于 componentDidMount
      
      // 等价于 class 组件的 componentDidUpdate ,仅针对监听的state
      useEffect(()=>{
          console.log('当count的state放生改变时触发 - componentDidUpdate')
      },[count])
      
      // 等价于 class 组件的 componentWillUnmount 
      useEffect(()=>{
          return () => {
              console.log('componentWillUnmount')
          }
      },[])
      
      
    4. 可以将 useEffect 加上参数组合的方式,来实现以下三种生命周期钩子效果.
      1. componentDidUpdate - 监听所有属性,或者某些属性
      2. componentWillUnmount - 当函数式组件销毁时
      3. componentDidMount - 不监听任意属性.
    useEffect模拟三种事件钩子

    React.useRef

    1. React.useRef 可以在函数式组件中存储/查找组件内标签或任意其他数据
    2. 语法: const ref = React.useRef(), 然后再你需要引用的组件或者dom元素上使用 ref={ref} 即可.
    3. 作用: 用于保存标签对象,功能于 React.createRef 一致.
    4. useRef 既可以引用DOM 也可以引用组件实例.
    import React, { useRef } from 'react';
    import Test from './test';
    
    function Demo(props) {
      // 声明一个input的引用
      const inputRef = useRef()
      // 能引用一个组件吗? -> 结论,可以
      const testRef = useRef()
      const show = () => {
        // alert(document.getElementById('abc').value)
        alert(inputRef.current.value)
      }
    
      const test = () => {
        console.log(testRef.current)
        testRef.current.show()
      }
    
      return (
        <div>
          <input type="text" id="abc" ref={inputRef} />
          <button onClick={show}>展示input的内容</button>
          {/* 这里是引用一个组件 */}
          <Test ref={testRef}></Test>
          <button onClick={test}>测试ref是否能引用一个组件实例</button>
        </div>
      );
    }
    
    export default Demo;
    

    Fragment

    <Fragment></Fragment>
    <></>
    

    作用:

    可以不必拥有一个真实的dom.用上述两种标签占位!


    createContext

    一种祖孙组件间的通信方式,类似于Vue的 provider/inject .

    使用方式:

    1. 创建context容器对象
        const Context = React.createContext()
    2. 渲染子组件时,外面包裹 <Context.Provider></Context.Provider>, 并通过value属性,将需要传递给后代组件.
        <Context.Provider value={{...this.state}}>
            <Other />
        </Context.Provider>
    3. Other 组件以及后面的所有组件,就都可以通过某些设置,拿到value的值.
    4. 后代组件读取数据.
        1. class 组件 (仅用于class组件)
            static contextType = Context
            this.context = 就能拿到value的值.
        2. 函数式组件 (函数式组件和类式组件都可以)
            <Context.Consumer>
                {
                    (value) => {
                        // value 就是context提供的值.
                        return <li>{value.name} - {value.age}</li>
                    }
                }
            </Context.Consumer>    
    

    演示代码如下:

    import React, { Component } from 'react'
    import './index.scss'
    
    // 创建context上下文组件
    const NameContext = React.createContext()
    
    export default class A extends Component {
      state = { name: 'tom' }
      render() {
        return (
          <div className='A-box'>
            <h2>我是A组件</h2>
            <h3>我的用户名是:{this.state.name}</h3>
            {/* 第一步 */}
            <NameContext.Provider value={{ ...this.state }}>
              <B name={this.state.name} />
            </NameContext.Provider>
          </div>
        )
      }
    }
    
    class B extends Component {
      // 第二步,声明接受contextType
      static contextType = NameContext
      render() {
        return (
          <div className='B-box'>
            <h2>我是B组件</h2>
            {/* 父子组件间可以使用props传递 */}
            {/* <h3>我从A组件拿到的用户名是:{this.props.name}</h3> */}
            <h3>我从A组件拿到的用户名是:{this.context.name}</h3>
            <C />
          </div>
        )
      }
    }
    
    class C extends Component {
      // 第二步,声明接受contextType
      static contextType = NameContext
      render() {
        // console.log(this);
        return (
          <div className='C-box'>
            <h2>我是C组件</h2>
            <h3>我从A组件拿到的用户名是:{this.context.name}</h3>
            <D />
          </div>
        )
      }
    }
    // 函数式组件
    function D() {
      return (
        <div className='D-box'>
          <h2>我是D组件</h2>
          <h3>我从A组件拿到的用户名是:
            <NameContext.Consumer>
              {
                value => {
                  return `${value.name}`
                }
              }
            </NameContext.Consumer>
    
          </h3>
        </div>
      )
    }
    

    组件渲染优化

    React.Component 的 2 个问题

    1. 只要执行了setState,发生了状态改变,不论state状态是否在模板上显示,都会重新调用 render 函数.但组件效率低.
    2. 如果父组件重新render了,那么子组件也会被render,多组件效率低下.

    效率高的做法

    只有当依赖的state或者props,在视图中显示时,才调用render函数.

    问题核心:

    React 中,你只要调用了 setState 就会重新render,因为 shouldComponentUpdate() 钩子函数总是返回true.

    解决办法:

    1. 重写 shouldComponentUpdate,比较 stateprops 来决定是否调用 render 渲染函数.
    2. 使用 PureComponent
      PureComponent 重写了 shouldComponentUpdate, 只有当 state 或者 props 有变化时才返回为 true
      注意: 只是进行 stateprops 的浅层比较. 如果只是对象内部的数据发生了变化,直接返回false.你必须在setState里传递一个新的对象
      项目中,一般使用 PureComponent 用来进行渲染性能优化.

    演示代码:

    import React, { Component } from 'react'
    
    // // 测试代码一
    // export default class Demo extends Component {
    //   update = () => {
    //     this.setState({})
    //   }
    //   render() {
    //     console.log('React render 很蠢,只要你调用了setState,而不管状态是否依赖在视图上,都会执行render函数!原因是 componentShouldUpdate 默认总是返回true')
    //     return (
    //       <div>
    //         <h1>React render 很蠢,只要你调用了setState,而不管状态是否依赖在视图上,都会执行render函数!原因是 componentShouldUpdate 默认总是返回true</h1>
    
    //         <button onClick={this.update}>无视图依赖更新</button>
    //       </div>
    //     )
    //   }
    // }
    
    // 测试代码二,自己来优化,只有当props和state发生改变时,才更新视图,重新调用render 函数
    // export default class Demo extends Component {
    //   state = {
    //     username: '张三'
    //   }
    //   changeName = () => {
    //     this.setState({ username: '李四' })
    //     // this.setState({}) 如果传空对象,那么 nextState 和 this.state 时同一个对象.
    //     // username:undefined
    //   }
    //   /**
    //    * 
    //    * @param {*} newProps  更新后的 props 
    //    * @param {*} newState  更新后的 state
    //    */
    //   shouldComponentUpdate(nextProps, nextState) {
    //     // console.log(this.state, nextState);
    //     const oldStateStr = JSON.stringify(this.state)
    //     const newStateStr = JSON.stringify(nextState)
    //     const oldPropsStr = JSON.stringify(this.props)
    //     const newPropsStr = JSON.stringify(nextProps)
    //     // 当新旧props和state发生变化时,才需要render.
    //     return oldStateStr !== newStateStr || oldPropsStr !== newPropsStr
    //   }
    
    //   render() {
    //     console.log(`render`);
    //     const { username } = this.state
    //     return (
    //       <div>
    //         <h1>用户的名字是:{username}</h1>
    //         <button onClick={this.changeName}>点我修改用户的名字为李四</button>
    //         {/* 没依赖父组件的数据,没接受props, 就没有必要render */}
    //         <Child />
    //       </div>
    //     )
    //   }
    // }
    
    // class Child extends React.Component {
    //   // 由于默认情况下,父组件render 会导致子组件render.所以,对于某些静态组件,可以直接返回false,不需要render
    //   shouldComponentUpdate(nextProps, nextState) {
    //     // 对于纯展示的静态组件,可以直接返回false
    //     return false
    //   }
    
    //   render() {
    //     console.log('child-render');
    //     return (
    //       <h1>纯静态组件,无需调用render</h1>
    //     )
    //   }
    // }
    
    
    // 测试代码三,使用 PureComponent
    // PureComponent 自动帮我们实现了 shouldComponentUpdate 函数,但是只有一层比较.
    export default class Demo extends React.PureComponent {
      state = {
        name: '李四',
        age: 22, // 浅层
        info: {
          address: '湖北', // 深层
          zicode: 43333
        }
      }
      changeName = () => {
        this.setState({
          name: '张三'
        })
        // const obj = this.state
        // obj.name = '李四'
        // this.setState(obj) // 注意,当你继承了PureComponent,那么 setState 里必须传递一个新的对象!否则,setState 函数内部拒绝执行之后的代码 
        /**
         * setState (stateChange) {
         *  if (orginState === stateChange) return 
         * }
         * 
         */
    
      }
    
      changeAge = () => {
        this.setState({
          age: 33
        })
      }
    
      changeAddress = () => {
        // 对于大于一层属性的修改,PureComponent 不会进行对比前后是否相等,而是直接调用render
        this.setState({
          info: {
            address: '武汉'
          }
        })
      }
    
      changeZiCode = () => {
        // 对于大于一层属性的修改,PureComponent 不会进行对比前后是否相等,而是直接调用render
        this.setState({
          info: {
            zicode: 123456
          }
        })
      }
    
      render() {
        console.log('render');
        const { name, age, info: { address, zicode } } = this.state
    
        return (
          <React.Fragment>
            <h3>name(浅层属性):{name}</h3>
            <button onClick={this.changeName}>修改name</button>
            <h3>age:(浅层属性):{age}</h3>
            <button onClick={this.changeAge}>修改age</button>
            <h3>address:(深层属性):{address}</h3>
            <button onClick={this.changeAddress}>修改address</button>
            <h3>zicode:(深层属性):{zicode}</h3>
            <button onClick={this.changeZiCode}>修改zicode</button>
    
          </React.Fragment>
        )
      }
    }
    
    

    使用 PureComponent 的注意点:

    必须给setState传递一个新的对象,否则 setState 不做任何操作!

     // const obj = this.state
        // obj.name = '李四'
        // this.setState(obj) // 注意,当你继承了PureComponent,那么 setState 里必须传递一个新的对象!否则,setState 函数内部拒绝执行之后的代码 
        /**
         * setState (stateChange) {
         *  if (orginState === stateChange) return 
         * }
         * 
         */
    

    render props

    如何在 React 里实现类似 Vue 插槽的技术?

    1. 使用 render props

    请查看代码

    import React, { PureComponent } from 'react'
    import './index.scss'
    export default class Demo extends PureComponent {
    
      render() {
        return (
          <div className='A-box'>
            <h2>我是最外层组件</h2>
            {/* 给Child组件传递了一个render属性,是一个函数,函数返回一个组件实例 */}
            <Child render={(name) => (<Demo2 name={name} />)} />
          </div>
        )
      }
    }
    
    
    class Child extends PureComponent {
      state = {
        name: 'Hello World In Child Component'
      }
      render() {
        return (
          <div className='B-box'>
            <h2>我是Child组件,我的state值是:{this.state.name}</h2>
            {/* 拿到props里的render,然后去调用这个实例 第一种实现插槽的方式 */}
            {this.props.render(this.state.name)}
          </div>
    
        );
      }
    }
    
    function Demo2(props) {
      return (
        <div className='C-box'>
          <h2>
            我是Demo2函数式组件,我由Child组件渲染.
          </h2>
          <h3>收到来自Child组件的数据:{props.name}</h3>
        </div>
      )
    }
    
    1. 使用 this.props.children

    查看代码:

    class Child2 extends React.PureComponent {
      render() {
        return (
          <div className='B-box'>
            <h2>我是Child2组件</h2>
            {/* 第二种实现插槽的方式 */}
            {this.props.children}
          </div>
        )
      }
    }
    
     <Child2>
              <Demo2 name="来自Child2的数据" />
    </Child2>
    
    function Demo2(props) {
      console.log(props);
      return (
        <div className='C-box'>
          <h2>
            我是Demo2函数式组件,我由Child组件渲染.
          </h2>
          <h3>收到来自Child组件的数据:{props.name}</h3>
        </div>
      )
    }
    
    

    错误边界

    理解:

    错误便捷(Error boundary);用来解决后代组件的错误,渲染出备用页面,不至于内部的某个组件发生错误,导致整个程序全部崩溃!

    特点:

    只能捕获后代组件[生命周期里]发生的错误,不能捕获自身由于用于操作,定时器,以及合成事件里的错误! 你可以理解为,只能捕获组件第一次初始化渲染时候发生的错误!

    演示代码:

    import React, { PureComponent } from 'react'
    import './index.scss'
    
    export default class Parent extends PureComponent {
      state = {
        hasError: '' // 用于标识子组件是否发生错误?
      }
      // 当 parent的所有后代出现error时,会出发此函数的调用,并携带error参数
      static getDerivedStateFromError(error) {
        console.log(error)
        return {
          hasError: error
        }
      }
    
      componentDidCatch(error) {
        // 可以收集到错误,并发送给后台统计.
        console.log("&&&", error)
      }
    
      render() {
        return (
          <div className='A-box'>
            <h2>我是Parent组件组件</h2>
            {this.state.hasError ? <h2 className='B-box'>当前网络不稳定,请稍后再试....</h2> : <Child />}
          </div>
        )
      }
    }
    
    class Child extends PureComponent {
      state = {
        users: undefined
      }
      happen = () => {
        // 这种错误无法捕获,只有在生命周期里的错误才能捕获!
        throw new Error('在child组件里发生了一个错误!')
      }
      render() {
        return (
          <div className="B-box">
            <h2>我是Child组件</h2>
            <ul>
              {this.state.users.map(user => (<li key={user.id}>{user.name}</li>))}
            </ul>
            <button onClick={this.happen}>点我发生一个错误!</button>
          </div>
        )
      }
    
    }
    
    

    组件间通信的方式

    组件间的关系:

    1. 父子组件
    2. 兄弟组件(非嵌套关系)
    3. 祖孙组件

    集中通信方式:

    1. props
      • 父 -> 子 props.everyThing 子 -> 父 props.function
      • render props 插槽.
    2. 消息发布订阅
      • pubsub / eventBus 等.
    3. 状态集中式管理
      • redux / dva 等等
    4. context
      • 类似 Vue 的 Provider / inject

    相关文章

      网友评论

          本文标题:react 学习笔记一

          本文链接:https://www.haomeiwen.com/subject/agzlhrtx.html