乐趣区

关于前端:day27-特殊型前端有哪些处理加载和渲染的特殊模式

组件化的模式

为什么说组件化在前端,特地是基于 JS 开发的 React 框架中,有着十分重要的地位呢?
一是它能够达到和桌面利用相似的性能
二是这样节俭了资源在咱们的手机或是 PC 上的下载和存储
三是因为这样能够让咱们随时随地拜访咱们须要的内容,只有有网络,输出一个 URL 便能够应用

在 React 中的组件化和咱们通常理解的 Web Component 是有区别的。
Web Component 的时候,更多关注的是组件的封装和重用,也就是经典的面向对象的设计模式思维。React Component 更多关注的是,通过申明式的形式更好地让 DOM 和状态数据之间同步。
React 推出了一些不同的模式:上下文提供者 渲染属性 高阶组件 和起初呈现的 Hooks

咱们能够大体将这些组件化的模式分为两类,一类是在 Hooks 呈现之前的上下文提供者、渲染属性、高阶组件模式,一类是 Hooks 呈现后带来的新模式。

经典模式

上下文提供者模式(Context Provider Pattern):通过创立上下文将数据传给多个组件的组件化形式。
它的作用是能够防止 prop-drilling,也就是防止将数据从父组件逐层下传到子组件的繁琐过程。
举个例子,如果咱们有一个菜单,外面蕴含了一个列表和列表元素。通过以下代码,咱们看到如果将数据一层层传递,就会变得十分繁琐。

function App() {const data = { ...}
  return (<Menu data={data} />);
}

var Menu = ({data}) => <List data={data} />
var List = ({data}) => <ListItem data={data} />
var ListItem = ({data}) => <span>{data.listItem}</span> 

而通过 React.createContext,咱们创立一个主题。之后通过 ThemeContext.Provider,咱们能够创立一个相干的上下文。这样咱们无需将数据一一传递给每个菜单里的元素,便能够让上下文中的元素都能够获取相干的数据。

var ThemeContext = React.createContext();

function App() {var data = {};
  return (<ThemeContext.Provider value = {data}>
        <Menu />
      </ThemeContext.Provider>  
  )
}

通过 React.useContext,能够获取元素上下文中的数据来进行读写。
渲染属性模式(Render Props Pattern)
比方在上面的价格计算器的例子中,咱们想让程序依据输出的产品购买数量计算出价格。然而,在没有渲染属性的状况下,计算价格的组件并拿不到输出购买的数量,所以计算不出价格。

export default function App() {
  return (
    <div className="App">
      <h1> 价格计算器 </h1>
      <Input />
      <Amount />
    </div>
  );
}

function Input() {var [value, setValue] = useState("");
  return (
    <input type="text" 
      value={value} 
      placeholder="输出数量"
      onChange={e => setValue(e.target.value)} 
    />
  );
}

function Amount({value = 0}) {return <div className="amount">{value * 188}元 </div>;
}

为了解决这个问题,咱们就能够用到 render props,把 amount 作为 input 的子元素,在其中传入 value 参数。也就是说通过渲染属性,咱们能够在不同的组件之间通过属性来共享某些数据或逻辑。

export default function App() {
  return (
    <div className="App">
      ...
      <Input>
        {value => (
          <>
            <Amount value={value} />
          </>
        )}
      </Input>
    </div>
  );
}

function Input() {
  ...
  return (
    <>
      <input ... />
      {props.children(value)}
    </>  
  );
}

function Amount({value = 0}) {...}

高阶组件模式(HOC,Higher Order Components Pattern):就是咱们能够把一个组件作为参数传入,并且返回一个组件。
![图片]
那么它有什么利用呢?假如在没有高阶组件的状况下,咱们想给一些按钮或文字组件减少一个圆边,可能要批改组件内的代码,而通过高阶函数,咱们能够在原始的直角的文字框和按钮组件的根底下面包装一些办法来失去圆边的成果。在理论的利用中,它能够起到相似于“装璜器”的作用。它不仅让咱们不须要对组件自身做批改,而且还能够让咱们重复使用形象进去的性能,防止代码冗余。

// 高阶函数
var enhancedFunction = higherOrderFunction(originalFunction);
// 高阶组件
var enhancedComponent = higherOrderComponent(originalComponent);

// 高阶组件作为装璜器
var RoundedText = withRoundCorners(Text);
var RoundedButton = withRoundCorners(Button);

Hooks 模式

Hooks 最间接的作用是能够用函数来代替 ES6 引入的新的 class 创立组件。
传统 class 形式创立

class App extends React.Component {constructor(props) {super(props);
    this.state = {count: 0};
  }
  render() {
    return (<button onClick={() => this.setState({count: this.state.count + 1})}>
        点击了 {this.state.count} 次。</button>
    );
  }
}

通过 Hook 形式创立

import React, {useState} from 'react';
function APP() {var [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}> 点击了 {count} 次。</button>
    </div>
  );
}

在这个例子中,你能够看到,咱们方才是通过用解构(destructure)的形式,创立了两个计数状态的变量,一个是 count,另外一个是 setCount。这样当咱们将这两个值赋值给 userState(0) 的时候,它们会别离被赋值为获取计数和更新计数。

// 数组解构
var [count, setCount] = useState(0);

// 上等同于
var countStateVariable = useState(0); 
var count = countStateVariable[0]; 
var setCount = countStateVariable[1];

Hooks 另外的一个作用是 能够让组件按性能解耦、再按相关性组合的性能
比方在没有 Hooks 的状况下,咱们可能须要通过组件的生命周期来组合性能。如果咱们用的是同一个组件的生命周期 componentDidMount 治理,那就会将不相干的性能聚合在了一起,而通过 useEffect 这样的一个 Hook,就能够把不相干的性能拆开,再依据相关性聚合在一起。
Hooks 还能够 让逻辑在组件之间更容易共享

加载渲染模式

渲染模式
前端单页利用(SPA)带来了不便的同时,也会造成性能上问题,比方它的 FCP(First Contentful Paint,首次内容绘制工夫)、LCP(Largest Contentful Paint,最大内容绘制工夫)、TTI(Time to Interactive,首次可交互工夫)会比拟长。
前端渲染除了性能上的问题,还会造成 SEO 的问题。通常为了解决 SEO 的问题,一些网站会在 SPA 的根底上再专门生成一套供搜索引擎检索的后端页面。然而作为搜寻的入口页面,后端渲染的页面也会被拜访到,它最大的问题就是到第一字节的工夫(TTFB)会比拟长。

为了解决前端和后端渲染的问题,动态渲染(static rendering)的概念便呈现了。动态渲染应用的是一种预渲染(pre-render)的形式。也是说在服务器端事后渲染出能够在 CDN 上缓存的 HTML 页面,以后端发动申请的时候,间接将渲染好了的文件发送给后端,通过这种形式,就升高了 TTFB。
动态渲染个别被称之为动态生成(SSG,static generation),而由此,又引出了动态渐进生成(iSSG,incremental static generation)的概念。
动态生成个别用于解决动态内容。而对于须要动静更新的页面就要用到动态渐进生成。iSSG 能够在 SSG 的根底上做到对增量页面的生成和存量局部的再生成。

尽管页面内容分为动态和动静之分,但有些页面总体上是不须要频繁交互的,如果动态页面须要相干的行为互动的状况下。SSG 就只能保障 FCP,然而很难保障 TTI 了。动态加载的元素赋予动静的行为。而在用户发动交互的时候,再水合的动作就是渐进式水合(progressive hydration)。
除了动态渲染和水合能够渐进外,后端渲染也能够通过 node 中的流(stream)做到后端渐进渲染(progressive SSR)。通过流,页面的内容能够分段传到前端,前端能够先加载先传入的局部。除了渐进式水合外,选择性水合能够利用 node stream 暂缓局部的组件传输,而将先传输到前端的局部进行水合,这种形式就叫做选择性水合(selective hydration)。

还有一种集大成的模式叫做岛屿架构(islands architecture):就如同咱们在地理课学到的,所有的大陆都能够看作是漂流在陆地上的“岛屿”一样,这种模式把页面上所有的组件都看成是“岛屿”。把动态的组件视为动态页面“岛屿”,应用动态渲染;而对于动静的组件则被视为一个个的微件“岛屿”,应用后端加水合的形式渲染。
加载模式
配合上述的渲染模式,相应的也须要对应的加载模式,对于动态内容,就通过动态倒入;动静的内容则通过动静倒入。基于渐进的思维,咱们也能够在局部内容流动到特定区域或者交互后,将须要展现的内容渐进地导入。被导入的内容能够通过宰割打包(bundle splitting),依据门路(route based splitting)来做相干组件或资源的加载。

PRPL(Push Render, Pre-Cache, Lazy-load):PRPL 模式的核心思想是在初始化的时候,先推送渲染最小的初始化内容。之后在背地通过 service worker 缓存其它常常拜访的路由相干的内容,之后当用户想要拜访相干内容时,就不须要再申请,而间接从缓存中懒加载相干内容。
PRPL 的思维是如何实现的呢?
背景:相比 HTTP1.1,HTTP2 中提供的服务器推送能够一次把初始化所须要的资源以外的额定素材都一并推送给客户端,PRPL 就是利用到了 HTTP2 的这个特点。可是光有这个性能还不够,因为尽管这些素材会保留在浏览器的缓存中,然而不在 HTTP 缓存中,所以用户下次访问的时候,还是须要再次发动申请。
解决问题计划:PRPL 就用到了 service worker 来做到将服务器推送过去的内容做预缓存。同时它也用到了代码宰割(code splitting),依据不同页面的路由需要将不同的组件和资源宰割打包,来按需加载不同的内容。
还有一个咱们须要留神的概念就是,pre-fetch 不等于 pre-load。pre-fetch 更多指的是事后从服务器端获取,目标是缓存后,便于之后须要的时候能疾速加载。而预加载则相同,是加载特地须要在初始化时应用的素材的一种形式,比方一些非凡字体,咱们心愿事后加载,等有内容加载时能顺滑地展现正确款式的字体。

性能优化模式
摇树优化
摇树优化的作用是移除 JavaScript 上下文中未援用的代码(dead-code)。那为什么咱们须要移除这些代码呢?因为这些未被应用的代码如果存在于最初加载的内容中,会占用带宽和内存,而如果它们并不会在程序执行中用到,那就能够被优化掉了。

虚构列表优化
它名字中的“虚拟化”一词从何而来呢?这就有点像咱们在量子力学外面提到的“薛定谔的猫”思维,就是咱们眼前的事物只有在观测的一瞬间才会被渲染进去,在这里咱们的世界就如同是“虚构”的沙箱。而在虚构列表中,咱们同样也只关注于渲染窗口挪动到的地位。这样就能够节俭算力和相干的耗时。

在基于 React 的三方工具中,有反对虚构列表优化的 react-window 和 react-virtualized。

此文章为 2 月 Day6 学习笔记,内容来源于极客工夫《Jvascript 进阶实战课》,大家共同进步💪💪

退出移动版