0에서 1까지 React 시리즈 구현: 수명주기 및 차이점 구현

php是最好的语言
풀어 주다: 2018-07-27 14:22:24
원래의
1247명이 탐색했습니다.

0에서 1까지 React 시리즈 구현: 수명주기 및 차이점 구현

이 기사 시리즈는 React 프레임워크(JSX/가상 DOM/구성 요소/라이프 사이클/diff 알고리즘/...)의 백본 콘텐츠를 정리하면서 (x)react를 구현합니다.

  • 구현을 0에서 1 React 시리즈 - JSX 및 Virtual DOM

  • React 시리즈를 0부터 1까지 구현 - 구성 요소 및 상태|props

lifecycle

먼저 다음과 같이 흐름도로 표현되는 React의 라이프사이클을 검토해 보겠습니다.

0에서 1까지 React 시리즈 구현: 수명주기 및 차이점 구현

이 흐름도는 React의 수명주기를 비교적 명확하게 보여줍니다. 생성기간, 존재기간, 소멸기간의 3단계로 구분된다.

사용자 컴포넌트에 라이프 사이클 후크 기능이 있으므로 이전 _render 기능을 다음과 같이 일부 조정합니다.

// 原来的 _render 函数,为了将职责拆分得更细,将 virtual dom 转为 real dom 的函数单独抽离出来
function vdomToDom(vdom) {
  if (_.isFunction(vdom.nodeName)) {        // 为了更加方便地书写生命周期逻辑,将解析自定义组件逻辑和一般 html 标签的逻辑分离开
    const component = createComponent(vdom) // 构造组件
    setProps(component)                     // 更改组件 props
    renderComponent(component)              // 渲染组件,将 dom 节点赋值到 component
    return component.base                   // 返回真实 dom
  }
  ...
}
로그인 후 복사

setProps 함수에 comComponentWillMount, 를 추가할 수 있습니다(렌더링 전) ) componentWillReceiveProps 메소드, setProps 함수는 다음과 같습니다: componentWillMountcomponentWillReceiveProps 方法,setProps 函数如下:

function setProps(component) {
  if (component && component.componentWillMount) {
    component.componentWillMount()
  } else if (component.base && component.componentWillReceiveProps) {
    component.componentWillReceiveProps(component.props) // 后面待实现
  }
}
로그인 후 복사

而后我们在 renderComponent 函数内加入 componentDidMountshouldComponentUpdatecomponentWillUpdatecomponentDidUpdate

function renderComponent(component) {
  if (component.base && component.shouldComponentUpdate) {
    const bool = component.shouldComponentUpdate(component.props, component.state)
    if (!bool && bool !== undefined) {
      return false // shouldComponentUpdate() 返回 false,则生命周期终止
    }
  }
  if (component.base && component.componentWillUpdate) {
    component.componentWillUpdate()
  }

  const rendered = component.render()
  const base = vdomToDom(rendered)

  if (component.base && component.componentDidUpdate) {
    component.componentDidUpdate()
  } else if (component && component.componentDidMount) {
    component.componentDidMount()
  }

  if (component.base && component.base.parentNode) { // setState 进入此逻辑
    component.base.parentNode.replaceChild(base, component.base)
  }

  component.base = base  // 标志符
}
로그인 후 복사

그런 다음 comComponentDidMount, shouldComponentUpdate, comComponentWillUpdate, 를 추가합니다. componentDidUpdate 메소드

class A extends Component {
  componentWillReceiveProps(props) {
    console.log('componentWillReceiveProps')
  }

  render() {
    return (
      <p>{this.props.count}</p>
    )
  }
}

class B extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 1
    }
  }

  componentWillMount() {
    console.log(&#39;componentWillMount&#39;)
  }

  componentDidMount() {
    console.log(&#39;componentDidMount&#39;)
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log(&#39;shouldComponentUpdate&#39;, nextProps, nextState)
    return true
  }

  componentWillUpdate() {
    console.log(&#39;componentWillUpdate&#39;)
  }

  componentDidUpdate() {
    console.log(&#39;componentDidUpdate&#39;)
  }

  click() {
    this.setState({
      count: ++this.state.count
    })
  }

  render() {
    console.log(&#39;render&#39;)
    return (
      <p>
        <button onClick={this.click.bind(this)}>Click Me!</button>
        <A count={this.state.count} />
      </p>
    )
  }
}

ReactDOM.render(
  <B />,
  document.getElementById(&#39;root&#39;)
)
로그인 후 복사

라이프 사이클 테스트

다음 사용 사례를 테스트하세요.

componentWillMount
render
componentDidMount
로그인 후 복사

페이지가 로드될 때의 출력은 다음과 같습니다.

shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
로그인 후 복사

버튼을 클릭할 때의 출력은 다음과 같습니다.

/**
 * 比较旧的 dom 节点和新的 virtual dom 节点:
 * @param {*} oldDom  旧的 dom 节点
 * @param {*} newVdom 新的 virtual dom 节点
 */
function diff(oldDom, newVdom) {
  ...
  if (_.isString(newVdom)) {
    return diffTextDom(oldDom, newVdom)   // 对比文本 dom 节点
  }

  if (oldDom.nodeName.toLowerCase() !== newVdom.nodeName) {
    diffNotTextDom(oldDom, newVdom)       // 对比非文本 dom 节点
  }

  if (_.isFunction(newVdom.nodeName)) {
    return diffComponent(oldDom, newVdom) // 对比自定义组件
  }

  diffAttribute(oldDom, newVdom)          // 对比属性

  if (newVdom.children.length > 0) {
    diffChild(oldDom, newVdom)            // 遍历对比子节点
  }

  return oldDom
}
로그인 후 복사

diff 구현

반응에서 diff 구현의 아이디어는 이전 가상 돔과 새 가상 돔을 비교하고 비교된 패치(패치)를 페이지에 렌더링하여 부분 새로 고침을 달성하는 것입니다. 이 기사에서는 diff 구현을 사용합니다. preact와 simple-react에서 전체적인 아이디어는 이전 가상 dom과 새 가상 dom을 비교하는 것입니다. dom 노드는 새로운 가상 dom 노드와 비교되며 다양한 비교 유형(텍스트 노드, 비-텍스트 노드)에 따라 해당 로직이 호출됩니다. 텍스트 노드, 사용자 정의 구성 요소)을 사용하여 페이지의 부분 렌더링을 달성합니다. 코드의 전체 구조는 다음과 같습니다.

// 对比文本节点
function diffTextDom(oldDom, newVdom) {
  let dom = oldDom
  if (oldDom && oldDom.nodeType === 3) {  // 如果老节点是文本节点
    if (oldDom.textContent !== newVdom) { // 这里一个细节:textContent/innerHTML/innerText 的区别
      oldDom.textContent = newVdom
    }
  } else {                                // 如果旧 dom 元素不为文本节点
    dom = document.createTextNode(newVdom)
    if (oldDom && oldDom.parentNode) {
      oldDom.parentNode.replaceChild(dom, oldDom)
    }
  }
  return dom
}
로그인 후 복사

다음은 다양한 비교 유형에 따라 해당 로직을 구현합니다.

텍스트 노드 비교

먼저 간단한 텍스트 노드를 비교합니다. 코드는 다음과 같습니다.

// 对比非文本节点
function diffNotTextDom(oldDom, newVdom) {
  const newDom = document.createElement(newVdom.nodeName);
  [...oldDom.childNodes].map(newDom.appendChild) // 将旧节点下的元素添加到新节点下
  if (oldDom && oldDom.parentNode) {
    oldDom.parentNode.replaceChild(oldDom, newDom)
  }
}
로그인 후 복사

비텍스트 노드 비교

비텍스트 노드를 비교합니다. 아이디어는 같은 수준의 이전 노드를 새 노드로 바꾸는 것입니다. 코드는 다음과 같습니다.

// 对比自定义组件
function diffComponent(oldDom, newVdom) {
  if (oldDom._component && (oldDom._component.constructor !== newVdom.nodeName)) { // 如果新老组件不同,则直接将新组件替换老组件
    const newDom = vdomToDom(newVdom)
    oldDom._component.parentNode.insertBefore(newDom, oldDom._component)
    oldDom._component.parentNode.removeChild(oldDom._component)
  } else {
    setProps(oldDom._component, newVdom.attributes) // 如果新老组件相同,则将新组件的 props 赋到老组件上
    renderComponent(oldDom._component)              // 对获得新 props 前后的老组件做 diff 比较(renderComponent 中调用了 diff)
  }
}
로그인 후 복사

사용자 지정 구성 요소 비교

사용자 지정 구성 요소를 비교하는 아이디어는 다음과 같습니다. 새 구성 요소와 이전 구성 요소가 다른 경우 새 구성 요소와 이전 구성 요소가 다른 경우 이전 구성 요소를 새 구성 요소로 직접 교체합니다. 마찬가지로 새 구성 요소의 소품을 이전 구성 요소에 할당한 다음 새 소품을 얻기 전과 후에 이전 구성 요소를 비교합니다. 코드는 다음과 같습니다.

// 对比子节点
function diffChild(oldDom, newVdom) {
  const keyed = {}
  const children = []
  const oldChildNodes = oldDom.childNodes
  for (let i = 0; i < oldChildNodes.length; i++) {
    if (oldChildNodes[i].key) { // 将含有 key 的节点存进对象 keyed
      keyed[oldChildNodes[i].key] = oldChildNodes[i]
    } else {                    // 将不含有 key 的节点存进数组 children
      children.push(oldChildNodes[i])
    }
  }

  const newChildNodes = newVdom.children
  let child
  for (let i = 0; i < newChildNodes.length; i++) {
    if (keyed[newChildNodes[i].key]) {  // 对应上面存在 key 的情形
      child = keyed[newChildNodes[i].key]
      keyed[newChildNodes[i].key] = undefined
    } else {                            // 对应上面不存在 key 的情形
      for (let j = 0; j < children.length; j++) {
        if (isSameNodeType(children[i], newChildNodes[i])) { // 如果不存在 key,则优先找到节点类型相同的元素
          child = children[i]
          children[i] = undefined
          break
        }
      }
    }
    diff(child, newChildNodes[i]) // 递归比较
  }
}
로그인 후 복사

하위 노드 탐색 및 비교

하위 노드 탐색 및 비교에는 두 가지 전략이 있습니다. 하나는 동일한 수준의 노드만 비교하는 것이고, 다른 하나는 노드에 핵심 속성을 추가하는 것입니다. 그들의 목적은 공간 복잡성을 줄이는 것입니다. 코드는 다음과 같습니다.

/**
 * 更改属性,componentWillMount 和 componentWillReceiveProps 方法
 */
function setProps(component, attributes) {
  if (attributes) {
    component.props = attributes // 这段逻辑对应上文自定义组件比较中新老组件相同时 setProps 的逻辑
  }

  if (component && component.base && component.componentWillReceiveProps) {
    component.componentWillReceiveProps(component.props)
  } else if (component && component.componentWillMount) {
    component.componentWillMount()
  }
}
로그인 후 복사
Test

라이프 사이클 섹션에서, componentWillReceiveProps 메소드는 아직 실행되지 않았습니다. setProps 함수를 약간 수정하세요.

rrreee

라이프 사이클 섹션의 마지막 테스트 사례를 테스트해 보겠습니다.
  • 라이프 사이클 테스팅

0에서 1까지 React 시리즈 구현: 수명주기 및 차이점 구현

  • diff 테스팅

0에서 1까지 React 시리즈 구현: 수명주기 및 차이점 구현

프로젝트 주소, 홍보 방법

관련 기사:

React 컴포넌트 라이프 사이클 인스턴스 분석

상세 React 구성 요소 수명 주기에 대한 설명

관련 동영상:

Virtual dom-React 프레임워크 동영상 튜토리얼

🎜

위 내용은 0에서 1까지 React 시리즈 구현: 수명주기 및 차이점 구현의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿