zoukankan      html  css  js  c++  java
  • 侯策《前端开发核心知识进阶》读书笔记——React基础

    JSX实现条件渲染

    渲染一个列表。但是需要满足:列表为空数组时,显示空文案“Sorry,the list is empty”。同时列表数据可能通过网络获取,存在列表没有初始值为 null 的情况。

    最简单的方式:三目运算

    const list = ({list}) => {
      const isNull = !list
      const isEmpty = !isNull && !list.length
    
      return (
        <div>
        {
          isNull
          ? null
          : (
              isEmpty 
              ? <p>Sorry, the list is empty </p>
              : <div>
                  {
                    list.map(item => <ListItem item={item} />)
                  }
                </div>
            )
        }
        </div>
      )
    }

    但是我们多加了几个状态:加上出现错误时,正在加载时的逻辑,三目运算符嵌套地狱就出现了,破解这种地狱的方法有两种:

    抽离出render function:

    const getListContent = (isLoading, list, error) => {
        console.log(list)
        console.log(isLoading)
        console.log(error)
       // ...
       return ...
    }
    
    const list = ({isLoading, list, error}) => {
      return (
        <div>
          {
            getListContent(isLoading, list, error)
          }
        </div>
      )
    }

    IIFE:

    const list = ({isLoading, list, error}) => {
      return (
        <div>
          {
            (() => {
              console.log(list)
              console.log(isLoading)
              console.log(error)
    
              if (error) {
                return <span>Something is wrong!</span>
              }
              if (!error && isLoading) {
                return <span>Loading...</span>
              }
              if (!error && !isLoading && !list.length) {
                return <p>Sorry, the list is empty </p>
              }
              if (!error && !isLoading && list.length > 0) {
                return <div>
                  {
                    list.map(item => <ListItem item={item} />)
                  }
                </div>
              }
            })()
          }
        </div>
      )
    }

    “为什么不能直接在 JSX 中使用 if…else,只能借用函数逻辑实现呢”?实际上,我们都知道 JSX 会被编译为 React.createElement。直白来说,React.createElement 的底层逻辑是无法运行 JavaScript 代码的,而它只能渲染一个结果。因此 JSX 中除了 JS 表达式,不能直接写 JavaScript 语法。准确来讲,JSX 只是函数调用和表达式的语法糖。

    虽然 JSX 只是函数调用和表达式的语法糖,但是 JSX 仍然具有强大而灵活的能力。React 组件复用最流行的方式都是在 JSX 能力基础之上的,比如 HoC,比如 render prop 模式:

    class WindowWidth extends React.Component {
      constructor() {
        super()
        this.state = {
           0
        }
      }
    
      componentDidMount() {
        this.setState(
          {
             window.innerWidth
          },
          window.addEventListener('resize', ({target}) => {
            this.setState({
               target.innerWidth
            })
          })
        )
      }
    
      render() {
        return this.props.children(this.state.width)
      }
    }
    
    <WindowWidth>
      {
        width => (width > 800 ? <div>show</div> : null)
      }
    <WindowWidth>

    还可以让 JSX 具有 Vue template 的能力:

    render() {
      const visible = true
    
      return (
        <div>
          <div v-if={visible}>
           content 
          </div>
        </div>
      )
    }
    
    render() {
      const list = [1, 2, 3, 4]
    
      return (
        <div>
          <div v-for={item in list}>
            {item}
          </div>
        </div>
      )
    }

    this.setState

    this.setSate 这个 API,官方描述为:

    setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.

    既然用词是 may,那么说明 this.setState 一定不全是异步执行,也不全是同步执行的。所谓的“延迟更新”并不是针对所有情况。

    实际上, React 控制的事件处理过程,setState 不会同步更新 this.state。而在 React 控制之外的情况, setState 会同步更新 this.state。

    控制内外怎么进行定义呢?

    onClick() {
      this.setState({
        count: this.state.count + 1
      })
    }
    
    componentDidMount() {
      document.querySelectorAll('#btn-raw')
        .addEventListener('click', this.onClick)
    }
    
    render() {
      return (
        <React.Fragment>
          <button id="btn-raw">
            click out React
          </button>
    
          <button onClick={this.onClick}>
            click in React
          </button>
        </React.Fragment>
      )
    }

    在这个case当中,id 为 btn-raw 的 button 上绑定的事件,是在 componentDidMount 方法中通过 addEventListener 完成的,这是脱离于 React 事件之外的,因此它是同步更新的。反之,代码中第二个 button 所绑定的事件处理函数对应的 setState 是异步更新的。

    异步化 

    官方提供了这种处理异步更新的方法。其中之一就是 setState 接受第二个参数,作为状态更新后的回调。这会带来的回调地狱问题显而易见。

    常见的处理方式是利用生命周期方法,在 componentDidUpdate 中进行相关操作。第一次 setState 进行完后,在其触发的 componentDidUpdate 中进行第二次 setState,依此类推。

    但是这样存在的问题也很明显:逻辑过于分散。生命周期方法中有很多很难维护的“莫名其妙操作”,出现“面向生命周期编程”的情况。

    解决回调地狱最直接的方案就是将 setState Promise 化:

    const setStatePromise = (me, state) => {
      new Promise(resolve => {
        me.setState(state, () => {
          resolve()
        })
      })
    }

    这只是 patch 做法,也可以修改React 源码:

    原生事件 VS React 合成事件

    React中的事件有以下几个特性:

    • React 中的事件机制并不是原生的那一套,事件没有绑定在原生 DOM 上 ,大多数事件绑定在 document 上(除了少数不会冒泡到 document 的事件,如 video 等)
    • 同时,触发的事件也是对原生事件的包装,并不是原生 event
    • 出于性能因素考虑,合成事件(syntheticEvent)是被池化的。这意味着合成事件对象将会被重用,在调用事件回调之后所有属性将会被废弃。这样做可以大大节省内存,而不会频繁的创建和销毁事件对象。

    这样的事件系统设计,无疑性能更加友好,但同时也带来了几个潜在现象。

    异步访问事件对象

    //我们不能以异步的方式访问合成事件对象:
    function handleClick(e) {
      console.log(e)
    
      setTimeout(() => {
        console.log(e)
      }, 0)
    }
    
    //undefined

    React 也贴心的为我们准备了持久化合成事件的方法

    function handleClick(e) {
      console.log(e)
    
      e.persist()
    
      setTimeout(() => {
        console.log(e)
      }, 0)
    }

    如何阻止冒泡

    在 React 中,直接使用 e.stopPropagation 不能阻止原生事件冒泡,因为事件早已经冒泡到了 document 上,React 此时才能够处理事件句柄。

    componentDidMount() {
      document.addEventListener('click', () => {
        console.log('document click')
      })
    }
    
    handleClick = e => {
      console.log('div click')
      e.stopPropagation()
    }
    
    render() {
      return (
        <div onClick={this.handleClick}>
          click
        </div>
      )
    }
    
    //div click
    //document click

    但是 React 的合成事件还给使用原生事件留了一个口子,通过合成事件上的 nativeEvent 属性,我们还是可以访问原生事件。原生事件上的 stopImmediatePropagation 方法:除了能做到像 stopPropagation 一样阻止事件向父级冒泡之外,也能阻止当前元素剩余的、同类型事件的执行(第一个 click 触发时,调用 e.stopImmediatePropagtion 阻止当前元素第二个 click 事件的触发)。因此这一段代码:

    componentDidMount() {
      document.addEventListener('click', () => {
        console.log('document click')
      })
    }
    
    handleClick = e => {
      console.log('div click')
      e.nativeEvent.stopImmediatePropagation()
    }
    
    render() {
      return (
        <div onClick={this.handleClick}>
          click
        </div>
      )
    }
    
    //div click

    Element diff

    react的diff算法,可以参看这篇文章https://www.jianshu.com/p/3ba0822018cf,讲得已经是非常的详细了。

    React 三个假设在对比 element 时,存在短板,于是需要开发者给每一个 element 通过提供 key ,这样 react 可以准确地发现新旧集合中的节点中相同节点,对于相同节点无需进行节点删除和创建,只需要将旧集合中节点的位置进行移动,更新为新集合中节点的位置。

    更新策略

    对于以下的情况:

    组件 1234,变为 2143,此时 React 给出的 diff 结果为 2,4 不做任何操作;1,3 进行移动操作即可。

    也就是元素在旧集合中的位置,相比新集合中的位置更靠后的话,那么它就不需要移动。当然这种 diff 听上去就并非完美无缺的。

    但是这样的移动策略在这种情况下代价就变得非常大:

    针对这种情况,官方建议:

    “在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作。”

    key值作用

    有一个问题是加了 key 一定要比没加 key 的性能更高吗?

    <div id="1">1</div>
    <div id="2">2</div>
    <div id="3">3</div>
    <div id="4">4</div>

    对于这种情况,当它变为 [2,1,4,5]:删除了 3,增加了 5,按照之前的算法,我们把 1 放到 2 后面,删除 3,再新增 5。整个操作移动了一次 dom 节点,删除和新增一共 2 处节点。

    由于 dom 节点的移动操作开销是比较昂贵的,其实对于这种简单的 node text 更改情况,我们不需要再进行类似的 element diff 过程,只需要更改 dom.textContent 即可。

    const startTime = performance.now()
    
    $('#1').textContent = 2
    $('#2').textContent = 1
    $('#3').textContent = 4
    $('#4').textContent = 5
    
    console.log('time consumed:' performance.now() - startTime)

    这么看,也许没有 key 的情况下要比有 key 的性能更好。

  • 相关阅读:
    洛谷P1908《逆序对》
    洛谷P3884《[JLOI2009]二叉树问题》
    最近公共祖先 LCA
    洛谷P1531《I Hate It》
    洛谷P1563「NOIP2016」《玩具谜题》
    乘法逆元求法
    CF56E 【Domino Principle】
    CF638C 【Road Improvement】
    Luogu
    2018.8.7提高B组模拟考试
  • 原文地址:https://www.cnblogs.com/fmyao/p/12826446.html
Copyright © 2011-2022 走看看