記一次preact遷移到react16.6.7的經歷

0. 前言

preact做爲備胎,可是具備體積小,diff算法優化過的特色,簡單活動頁用上它是不錯的選擇。可是考慮到react使人興奮的新特性,preact並無按時更新去徹底支持它,更嚴重的是一些babel插件、一些庫配合preact會有問題。因此,仍是不得不遷移了。javascript

如何遷移?package.json直接修改版本,刪掉preact,重裝,完事!java

too youngreact

1. 從alias改起

首先,通常是這樣子接入preact的,使得咱們代碼裏面毫無感受咱們用的是preact。在webpack的alias裏面配置:webpack

alias: {
    react: 'preact-compat',
    'react-dom': 'preact-compat'
  },
複製代碼

因此,第一步先把這個去掉web

2. 語法上

  1. preact的元素數組能夠不寫key,切換回來必然警告不少,須要把key補上
render() {
    return (
      [
        <div key="container">2</div>,
        <div key="more">1</div>,
      ]
    );
  }
複製代碼
  1. 元素的style能夠寫字符串,轉回react是報錯,致使頁面白屏
<div style={`background: url(${this.props.h5img});`} />
複製代碼
  1. will這些不安全的生命週期,須要手動修改
  2. state必須初始化,不能直接想有this.state.xx就有。必須保證後面用到this.state以前,對state有初始化,不然是null

3. preact相關的router遷移回react生態

首先,import的preact-router得換成react-router。而後,將對應的語法和生態遷移到react相關的。算法

preact-router:npm

<Router history={history}>
              <Main path="/" history={history} />
              <GetGiftForm path="/get_gift_form" history={history} />
              <Join path="/join" history={history} />
              <NewUser path="/new_user" history={history} />
          </Router>
複製代碼

react-router:json

<Router history={history}>
            <div>
              <Route exact path="/" history={history} component={Main} />
              <Route path="/get_gift_form" history={history} component={GetGiftForm} />
              <Route path="/join" history={history} component={Join} />
              <Route path="/new_user" history={history} component={NewUser} />
            </div>
          </Router>
複製代碼

preact-router有一個route方法,就是直接將路由push或者replace的,而react-router是沒有這個方法的。實際上底層就是封裝history路由加上內部的setstate:數組

import { route } from 'preact-router';
route('/a');
複製代碼

問題來了,若是沒有這個方法,想用腳本跳轉路由怎麼辦?直接history上改,只能改地址欄的url顯示但不能更新組件以及內部狀態。因此咱們只能找和react-router配合起來用的相關的庫。安全

觀察一下,都用到了history屬性,傳入一個history,這個是用了一個history的庫建立的,因此咱們儘量的讓它接入兩種路由獲得同樣的效果:

import createHashHistory from 'history/createHashHistory';
const history = createHashHistory();
複製代碼

打印了history,發現它有push、replace屬性,大概也猜到應該就是像route的效果的,一驗證發現可行:

history.push('/a');
複製代碼

另外,還有preact-router的路由更新監聽是這樣的:

<Router history={history} onChange={this.handleRoute}>
             ......
        </Router>
複製代碼

切換到react的話,沒有這個方法。因而咱們繼續找history,發現有一個listen屬性,看名字是一個監聽函數,也就是能夠實現路由更新監聽的:

history.listen((location) => {
      ...
    });
複製代碼

4. 異步路由

用preact-router的時候,有些組件是異步的:

<Router history={history}>
            { trackRoute() }
        </Router>
複製代碼

trackRoute函數組件:

import React from 'react';
import AsyncRoute from 'preact-async-route';
export default () => {
  return (
    <AsyncRoute path="/track" getComponent={async () => { return import('./comp' /* webpackChunkName: 'async_track' */); }} /> ); }; 複製代碼

效果就是,動態import,代碼分割。react也有一個相似的,react-async-router,可是用法和咱們的以前的preact-async-route差得遠並且不能優雅接入。既然是16.6.7了,咱們能夠試一下新特性:lazy+suspence

import React, { lazy, Suspense } from 'react';
const Comp = lazy(() => import('./comp' /* webpackChunkName: 'async_track' */));
function TrackRoute() {
  return (
    <Suspense fallback={<div />}> <Comp /> </Suspense>
  );
}
export default TrackRoute;
複製代碼

5. 內部實現原理不同的兼容

有一個頁面是這樣的:

// Main.jsx
render() {
    return (
      <div> <Page1 /> <Page2 /> <Page3 /> ... </div>
    );
  }
複製代碼

除了page1是原來就在的,其餘每個Pagex組件,返回Page組件,在Page內部,當頁碼是當前頁返回對應的元素,不然返回空:

// Pagex
render() {
    return (
        <Page />
    );
  }

// Page
render() {
    return currentPage === page ? <somedom> : null
  }
複製代碼

這裏,咱們能夠猜一下,Main是最大的組件,內部狀態頁碼在切換,全部的Pagex組件跟着更新,作出對應的變化。Pagex的更新,走的是didupdate。

實際上,preact的是第一個內部是Page實現的Pagex組件會unmount而後從新didmount。這裏是Page2先卸載再掛載,交換位置page1直接到page3的話也是page3先卸載再掛載。一些動畫操做就放在了didmount,以前都是這樣作的,但你們沒有發現是什麼問題,由於看見是這樣,開發起來沒毛病,又沒有bug,就不太在乎。切換回react,發現動畫不生效,才發現由於內部渲染機制不同致使的。因此咱們把函數的調用放在didupdate裏面,而且加上執行過一次的標記判斷。

6. 減小無必要的函數執行

getSnapshotBeforeUpdate

作一個像qq聊天起跑那樣的東西,頭和腳固定,中間無限長。這裏要求視覺給3個圖,頭、腳、中間1px高的圖。若是內容不滿屏,不顯示腳不能滾動,若是大於1屏顯示腳。

image

這裏固然少不了原生dom操做了,須要判斷高度,有沒有大於1屏,要不要overflow:hidden:

componentDidUpdate() {
    if (this.state.hasFooter) {
      return;
    }
     const last = document.querySelector('.act-card:last-of-type');
    if (last) {
      const { top } = last.getBoundingClientRect();
      if (SCREEN_HEIGHT - top < MAX_BOTTOM_DISTANCE) {
        setTimeout(() => {
          this.setState({ hasFooter: true }); // eslint:不能在didupdate裏面setstate
        });
        document.body.style.overflow = 'auto';
      } else {
        document.body.style.overflow = 'hidden';
      }
    }
  }
複製代碼

這裏須要執行兩次下面一大塊邏輯,第二次是無必要的,咱們能夠利用getSnapshotBeforeUpdate生命週期配合didupdate使用:

getSnapshotBeforeUpdate(_, prevstate) {
    if (!prevstate.hasFooter && prevstate.actCards.length) {
      return true;
    }
    return null;
  }

  componentDidUpdate(_, __, snapshot) {
    if (!snapshot) {
      return;
    }
    ......
  }
複製代碼

memo

能夠說函數式組件的purecomponent,並且第二個參數能傳入第二個相似shouldComponentUpdate的函數進行比較。既然能自定義化,那麼對比於purecomponent的自帶對象淺比較就是更加的靈活了,好比:

import React, { memo } from 'react';
export default memo((props) => {
  const isNoAct = !props.actCards.length || props.loading;
  return (
    <section className="body-container"> <div> { !isNoAct ? props.actCards.map((actCard, index) => ( <div key={index} > ...... </div> )) : ( <div className="no"> 暫無 </div> ) } </div> {props.hasFooter && <div className="body-footer" src={footer} alt="err" />} </section> ); }, (prevprops, nextprops) => { // 少渲染一次,一開始actcards什麼都沒有,咱們不比較actcards數組 if (prevprops.hasFooter !== nextprops.hasFooter || prevprops.loading !== nextprops.loading) { return false; } return true; }); 複製代碼

這裏咱們就少了一次從actcards數組的undefined[]的過程的比較,而這時候一直是loading狀態,沒有更新的意義。若是這裏return以前又是有像前面那個聊天氣泡那種效果須要dom操做的,那就傷性能了。這裏我列舉出來的只是把代碼刪減過的簡單結果,實際上開發的時候邏輯是遠遠比這demo複雜的

相關文章
相關標籤/搜索