React + Umi3 快速入门

为什么选择Umi?

它主要具备以下功能:可拓展、开箱即用、企业级、大量自研、完备路由、面向未来。

  • 为什么不是create-react-app?

create-react-app 是基于 webpack 的打包层方案,包含 build、dev、lint 等,他在打包层把体验做到了极致,但是不包含路由,不是框架,也不支持配置。所以,如果大家想基于他修改部分配置,或者希望在打包层之外也做技术收敛时,就会遇到困难。

代码仓库Github

React 核心内容梳理及自定义组件开发

源码目录: /src/pages/class

一、注意或技巧

  • 文件目录不能用关键字命名
  • vscode 插件project-tpl; 快速生成页面模块
  • vscode 插件JavaScript and TypeScript Nightly

二、React 组件生命周期

component-old.js
component-new.js
pure-component.js

三、React 组件通信(props和state)

目录: pages/class/lists

  • 父组件向子组件传值 (父组件绑定props给子组件使用)
  • 子组件向父组件传值 (父组件提供方法给子组件调用)
  • 兄弟组件之间传值 (使用父组件作为中间项)

思考:当兄弟组件层级嵌套太深,这种方法就不适用了。 dva

prop-types
主要作用:对props中数据类型进行检测及限制

import PropTypes from 'prop-types' // 引用

// 基本用法 用来检测数据类型
componentsName.PropTypes = {
  参数变量: PropTypes.类型
}
// 例子
myComponents.PropTypes = {
  name: Proptypes.string
}
复制代码

四、Dva数据处理及数据mock

dva 是一个基于redux 和 redux-saga 的数据流方案, 然后为了简化开发体验,dva还额外内置了react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。

demo dva 涉及目录

dva: {},                     // .umirc.ts 开启配置

/src/pages/class/dva         // 页面逻辑

/src/models/search.js        // 数据

/src/services/search.js      // 请求

/mock/search.js              // mock 数据

复制代码

示例流程:
异步调用:
在dva/search.js 输入aaa, 回车enter, 调用 models/search.js 的异步方法 getListsAsync,该异步方法再调用 services/search 里的 getLists方法 发起mock 请求,
接收/mock/search.js mock 返回的数据;models/search.js 里再调用同步方法 getLists, 数据重新渲染到页面。
同步调用:
在dva/search.js,也可以同步调用getLists
思考:如何开发的时候关闭mock?然后代理真实测试环境服务器
在.umirc.ts 关闭mock 增加 proxy配置

mock: false,
proxy: {
  '/api': {
    target: 'http://127.0.0.2:7002/',
    changeOrigin: true,
  },
},
复制代码

五、基于react context api 实现数据流管理

Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。
使用 context 的通用的场景包括管理当前的 locale,theme,或者一些缓存数据。

demo context 涉及目录


/src/pages/class/context         // 页面逻辑

/src/models/search.js        //

/src/services/search.js

/mock/search.js

复制代码

理解:

  • React.createContext:创建一个上下文的容器(组件), defaultValue可以设置共享的默认数据
const {Provider, Consumer} = React.createContext(defaultValue);
复制代码
  • Provider(生产者): 和他的名字一样。用于生产共享数据的地方。生产什么呢? 那就看value定义的是什么了。value:放置共享的数据。
<Provider value={/*共享的数据*/}>
  /*里面可以渲染对应的内容*/
</Provider>
复制代码
  • Consumer(消费者):这个可以理解为消费者。 他是专门消费供应商(Provider 上面提到的)产生数据。Consumer需要嵌套在生产者下面。才能通过回调的方式拿到共享的数据源。当然也可以单独使用,那就只能消费到上文提到的defaultValue
<Consumer>
  {value => /*根据上下文  进行渲染相应内容*/}
</Consumer>
复制代码

六、LazyLoad组件开发【基于lazy 与 suspense 实现的懒加载组件】

  • 启动按需加载: 该配置针对页面级别的按需加载
dynamicImport: {},
复制代码
  • 组件级别的懒加载

基于lazy 与 suspense 实现; 示例目录 /src/pages/class/lazy-load

  • 进一步封装

封装好的组件目录 components/LazyLoad
使用: 在/src/pages/class/context/index.js 下的lists 组件实现懒加载

  • 理解:

Suspense 让你的组件在渲染之前进行“等待”,并在等待时显示 fallback 的内容。

七、ErrorBoundary组件开发【基于React错误边界技术实现的组件】

常见问题:在render引入不存在的变量,直接导致白屏

  • 核心API: getDerivedStateFromError

在全局布局页面 /src/layouts 引入 ErrorBoundary组件
ErrorBoundary组件只能检测子组件发生的错误,不能检测本身发生的错误。
示例:pages/class/context

这个不是万能的,当遇到点击事件的内部函数、异步函数的内部函数报错是无法检测的。

八、Modal组件开发【基于createPortal 创建自定义弹窗组件】

示例目录 /src/pages/class/modal => /components/Modal => /components/CreatePortal

核心
ReactDOM.createPortals(child, container)
这个方法需要两个参数,第一个参数是需要挂载的组件实例,而第二个参数则是要挂载到的DOM节点。
一般来说第一个参数可能传递的是需要挂载的 this.props.children

九、使用 ref api 来操作dom和组件

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。对于这两种情况,React 都提供了解决办法。

核心:createRef 和 forwardRef (引用传递)

引用传递(Ref forwading)是一种通过组件向子组件自动传递 引用ref 的技术。

何时使用Ref ?
下面是几个适合使用 refs 的情况:

  • 管理焦点,文本选择或媒体播放。 (示例 /src/pages/class/refs)
  • 触发强制动画。
  • 集成第三方 DOM 库。

React Hooks 开发模式详解及自定义hook开发(16.8 的新特性)

源码目录: /src/pages/function

react hook api-新的组件开发模式

源码目录:/src/pages/function/hook

  • useState 方法

  • useEffect 方法

第一个参数:函数
第二个参数:没有传第二参数 页面每次改变都会刷新; []: 只在渲染的时候执行一次; [count]: count值改变的时候会重新执行useEffect

可以写多个,彼此之间互不影响

  • useEffect 和 useLayoutEffect 区别?

useEffect: 做异步相关的操作
useLayoutEffect: 操作dom对象或者改变显示效果
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新

  • useMeno

作用:主要做性能方面的优化,避免被误重新渲染
你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。

  • useCallback

作用:主要做性能方面的优化,
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。

useContext和useReducer实现数据流管理

  • useContext

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。

别忘记 useContext 的参数必须是 context 对象本身:
正确: useContext(MyContext)
错误: useContext(MyContext.Consumer)
错误: useContext(MyContext.Provider)
调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化。

  • useReducer

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。

useTitleHook【根据url修改页面title的自定义hook】

  • 目录

pages/function/customize
src/hooks/useTitleHook

  • 核心:利用了useLayoutEffect

useHttpHook【基于fetch api 封装具有监测功能的自定义hook】

  • 目录

pages/function/customize
src/hooks/useHttpHook

使用think-react-store实现数据处理【基于React context 和 hook的数据流解决方案】

think-react-store

  • 目录

pages/function/store

  • 安装
yarn add think-react-store
复制代码
  • 使用中间件 think-react-store/middlewares/log 打印日志
import log from 'think-react-store/middlewares/log';

// 注意是数组写法
<StoreProvider store={store} middleware={[log]}>
  <User />
</StoreProvider>
复制代码
  • 遇到问题

useDispatchHook(‘user’); 使用失败 报错

Fiber 架构

Fiber 是 React 16 中新的协调引擎。它的主要目的是使 Virtual DOM 可以进行增量式渲染。
Fiber 即是React新的调度算法
Fiber 架构将整个渲染阶段分为了调度阶段和提交阶段

  • Fiber 架构解决了什么问题

在页面元素很多,且需要频繁刷新的场景下,React 15 会出现掉帧的现象。
其根本原因,是大量的同步计算任务阻塞了浏览器的 UI 渲染。默认情况下,JS 运算、页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥的关系。
如果 JS 运算持续占用主线程,页面就没法得到及时的更新。
当我们调用setState更新页面的时候,React 会遍历应用的所有节点,计算出差异,然后再更新 UI。整个过程是一气呵成,不能被打断的。
如果页面元素很多,整个过程占用的时机就可能超过 16 毫秒,就容易出现掉帧的现象。

针对这一问题,React 团队从框架层面对 web 页面的运行机制做了优化,得到很好的效果。

  • Fiber 的实现

React 框架内部的运作可以分为 3 层:

Virtual DOM 层,描述页面长什么样。
Reconciler 层,负责调用组件生命周期方法,进行 Diff 运算等。
Renderer 层,根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative。
这次改动最大的当属 Reconciler 层了,React 团队也给它起了个新的名字,叫Fiber Reconciler。这就引入另一个关键词:Fiber。

先看一下stack-reconciler下的 React 是怎么工作的。代码中创建(或更新)一些元素, React 会根据这些元素创建(或更新)Virtual DOM,然后 React 根据更新前后 Virtual DOM 的区别,去修改真正的 DOM。注意,在 stack reconciler 下,DOM 的更新是同步的,也就是说,在 Virtual DOM 的比对过程中,发现一个 Instance 有更新,会立即执行 DOM 操作。

而Fiber Reconciler下,操作是可以分成很多小部分,并且可以被中断的,所以同步操作 DOM 可能会导致 fiber-tree 与实际 DOM 的不同步。对于每个节点来说,其不光存储了对应元素的基本信息,还要保存一些用于任务调度的信息。因此,fiber 仅仅是一个对象,表征 reconciliation 阶段所能拆分的最小工作单元,和上图中的 react instance一一对应。通过stateNode属性管理 Instance 自身的特性。通过child和sibling表征当前工作单元的下一个工作单元,return表示处理完成后返回结果所要合并的目标,通常指向父节点。整个结构是一个链表树。每个工作单元(fiber)执行完成后,都会查看是否还继续拥有主线程时间片,如果有继续下一个,如果没有则先处理其他高优先级事务,等主线程空闲下来继续执行。

Fiber 就是一种数据结构,它可以用一个纯 JS 对象来表示:

const fiber =  {
  stateNode: {}, // 管理 Instance 自身的特性
  child: {},     // 表征当前工作单元的下一工作单元
  sibling: {},   // 表征当前工作单元的下一工作单元
  return: {},    // 表示处理完成后返回结果所要合并的目标,通常指向父节点
}
复制代码

流程图
setState等修改state操作 => 放入更新队列 => 使用requestAnimationFrame处理高优先级任务(例如:用户点击,鼠标点击等操作) / 使用requestldleCallback 处理低优先级任务(查找根节点,或者转发成FiberNode) => 查找根节点将其转化成FiberNode => 处理下一个FiberNode(深度优先遍历) => 浏览器是否空闲

=> 否 => 任务暂停 => 空闲的时候继续执行 => 处理下一个FiberNode => 生成完整的diff effectList => 应用diff dom, 更新视图
=> 是 => 处理下一个FiberNode => 生成完整的diff effectList => 应用diff dom, 更新视图

  • 示例

当前页面包含一个列表,通过该列表渲染出一个 button 和一组 Item,Item 中包含一个 div,其中的内容为数字。通过点击 button,可以使列表中的所有数字进行平方。另外有一个按钮,点击可以调节字体大小。

页面渲染完成后,就会初始化生成一个fiber-tree,这一过程与初始化 Virtual DOM Tree 类似。

同时,React 还会维护一个workInProgressTree,workInProgressTree用于计算更新,完成 reconciliation 过程。

用户点击平方按钮后,利用各个元素平方后的 list 调用 setState,React 会把当前的更新送入 list 组件对应的update queue中。但是 React 并不会立即执行对比并修改 DOM 的操作。而是交给 scheduler 去处理。
scheduler 会根据当前主线程的使用情况去处理这次 update。为了实现这种特性,使用了requestIdelCallbackAPI。对于不支持这个 API 的浏览器,react 会加上 pollyfill。

总的来讲,通常,客户端线程执行任务时会以帧的形式划分,大部分设备控制在 30-60 帧是不会影响用户体验;在两个执行帧之间,主线程通常会有一小段空闲时间,requestIdleCallback可以在这个 空闲期(Idle Period) 调用 空闲期回调(Idle Callback),执行一些任务

1、低优先级任务由requestIdleCallback处理;
2、高优先级任务,如动画相关的由requestAnimationFrame处理;
3、requestIdleCallback可以在多个空闲期调用空闲期回调,执行任务;
4、requestIdleCallback方法提供 deadline,即任务执行限制时间,以切分任务,避免长时间执行,阻塞 UI 渲染而导致掉帧;

整个过程,简单来说,先通过requestIdleCallback获得可用的时间片,然后检查节点的update queue看是否需要更新,每处理完一个节点都会检查时间片是否用完,如果没用完,根据其保存的下一个工作单元的信息处理下一个节点。

  • Fiber 对hook的影响

  • Fiber 架构对组件生命周期的影响

调度阶段(尽量不使用不建议调度阶段的方法,比如请求接口操作)
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate

提交阶段(不能终止)
componentDidMound
componentDidUpdate
componentWillUnmount

  • 文档参考

React Fiber

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享