响应式系统与React|青训营笔记

66 阅读5分钟

响应式系统与React

这是我参与第五届青训营 伴学笔记创作活动的第5天。

课程重点

  • React的设计思路
  • React(hooks)的写法
  • React的实现
  • React状态管理库
  • 应用级框架科普

详细知识点

react的设计思路

原生js写前端页面的痛点:

  1. 状态更新,UI不会自动更新,需要手动地调用DOM进行更新。
  2. 欠缺基本的代码层面的封装和隔离,代码没有组件化。
  3. UI的数据之间的依赖关系需要手动去维护,如果依赖链路过长,则会遇到“回调地狱问题”。

所以我们希望能有一个框架(响应式)能帮助我们实现以下几点:

  1. 状态更新,UI自动更新。
  2. 前端代码组件化,可复用,可封装。
  3. 状态之间的互相依赖关系,只需要声明即可。例如:c与a,b的关系是c=a+b,而我们希望每次a或b改变时可以自动计算出c,而不需手动更新。

组件化

写代码时根据语义将页面划分为组件。

image.png 当前价格状态要放在Root结点,才能实现顶栏与配置面板共享价格数据,但这样的状态提升(状态归属于两个节点向上寻找到最近的祖宗节点) 比较危险,因为触发当前价格改变的事件在“型号选择”节点。

image.png 我们可以在Root组件内部定义一个onChangeValue()函数,将它向下传递给子组件,在子组件中触发这个函数,可以Root中的价格状态,但这样也有弊端,组件之间需要层层传递,并过于复杂,在实际开发中一般不会这样使用,后面介绍的redux状态管理会解决这个问题。但是这引发我们思考以下几个问题:

  1. React是单向数据流,还是双向数据流?
  2. 如何解决状态不合理上升的问题?
  3. 组件的状态改变后,如何更新DOM?

答案:1.React是单向数据流,只能是父组件给子组件传东西,但这并不代表子组件不可以改变父组件状态,比如父组件可以给子组件传一个函数,然后子组件去执行这个函数,从而达到我们的目的。2.将在后面的Redux状态管理库中解答。3.将在后面的讲解React的实现中解答。

组件总结: 组件是组件/原子组件的组合;组件内拥有的状态外部不可见;父组件可以将状态传递到子组件内部;组件设计的三条经验: 1.组件声明了状态和UI的映射;2.组件有Props(外部传递给它的状态)/State(内部私有状态)两种状态;3.组件可以由其它组件拼装而成。

React的生命周期

image.png

React(hooks)的写法

我们这一节就进入了实战阶段,下面主要介绍hooks的写法:

import React, { useState, useCallback } from 'react';
import ReactDom from 'react-dom';

const Test = function () {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return <div onClick={handleClick}>click me {count}</div>;
};

改变count,需要手动调用setCount(更新后的count)去修改,这是由于React做了层封装,count的状态是由react去接管的,react才可以做相应的状态更新(更新UI)。

import React, { useState, useCallback,useEffect } from 'react';
import ReactDom from 'react-dom';

const Test = function () {
  const [count, setCount] = useState(0);
  useEffect(()=>{//副作用
    document.title=`You clicked ${count} times`;
    console.log(document.title);
  },[count])
  const handleClick = useCallback(() => {
    setCount(count+1);
    
  }, [count]);

  return <div onClick={handleClick}>click me {count}</div>;
};
import React, { useState} from 'react';
import ReactDom from 'react-dom';

const Test = function () {
  const [x,setX]=useState(0);
  const [y,setY]=useState(0); 
  const sum=x+y;
  const multi=x*y;
  return <div>
  <h3>和是{sum},积是{multi}</h3>
  <h4>x:{x},y:{y}</h4>
  <button onClick={()=>setX(x+1)}>x+1</button>&nbsp;
  <button onClick={()=>setY(y+1)}>y+1</button>
  </div>
};

image.png useState传入一个初始值,返回一个状态和set该状态的函数,用户可以通过调用该函数,来实现状态的修改。

useEffect传入一个函数,和一个数组,数组是状态的数组,称作依赖项,该函数在mount时,和依赖项被set时会执行。

有“副作用”的函数,要传入useEffect来执行。副作用代表处理单纯的计算之外,还要做其它的一些事情,比如网络请求,更新DOM,localStorage存储数据等。

HOOK使用法则

  1. 不要在循环,条件或者嵌套函数中调用Hook:因为一个组件的状态存在是确定的,不会由是否满足某些条件而改变存在。
  2. 不能在普通函数中使用hook,hook必须在组件中或者另一个hook中被调用。
  3. 自己定义的hook须以"use"开头。

React的实现

初始问题

  1. JSX语法不符合JS标准语法,浏览器是不支持JSX的。
  2. 返回的JSX发生改变时,如何更新DOM.
  3. State/Props更新时,要重新触发render函数。

解决

问题1:

image.png 问题2: 真实的DOM不是JS中的对象,而是浏览器维护的一个状态树。 image.png Diff是一个递归函数,父组件的状态改变了,会递归地更新其所有的子组件,得到的虚拟DOM和真实DOM作对比,再更新真实DOM. image.png How to Diff: 要权衡两个互斥点:更新次数少、计算速度快。三条规则:

  1. 不同类型的元素:替换 (例如,div和span,则以它为父节点的子树整个都要替换)
  2. 同类型的DOM元素:更新 (例如,同类型的DOM元素某些属性改变了,就直接更新那些属性值)
  3. 同类型的组件元素:递归

React状态管理库

解决状态提升造成的根组件状态堆积的弊端。核心思想:将状态抽离到UI外部进行统一管理。 一般出现在业务代码里,组件库一般不会使用。

image.png 推荐的状态管理库:

image.png 科普一个概念:状态机: 当前状态收到外部事件,迁移到下一个状态。

image.png 哪些状态应该放在状态管理库里? 自己去衡量哪些状态需要被整个app共享,比如说用户头像等一些用户的信息一般需放在状态管理库中。

应用级框架科普

image.png next.js的一些内置配置

image.png modern.js内置配置

image.png

image.png

总结

以上内容只是基础入门,更多的React技术点还需要自己深入去学习研究,在此附上学习链接: react.docschina.org/; www.redux.org.cn/xu