react 学习笔记一

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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容