国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

徹底理清前端單頁面應用(SPA)的實現(xiàn)原理 【精讀源碼】

崔曉明 / 3325人閱讀

隨著React Vue前端框架的興起,出現(xiàn)了Vue-router,react-router-dom等前端路由管理庫,利用他們構(gòu)建出來的單頁面應用,也是越來越接近原生的體驗,再也不是以前的點擊標簽跳轉(zhuǎn)頁面,刷新整個頁面了,那么他們的原理是什么呢?
優(yōu)質(zhì)gitHub開源練手項目:

React移動端從零開始手寫一個優(yōu)化版腳手架

Electron跨平臺應用DEMO

手寫的Node.js靜態(tài)資源服務器

React 同構(gòu)ssr

Node.js爬蟲輸出PDF文件

先說說原始的MPA多頁面應用:
文末還有新建的QQ以及微信群哦~ 歡迎大家加入~~
傳統(tǒng)的多頁面應用構(gòu)建方式:

純服務端渲染,前后端不分離,使用jsp,jade,"ejs","tempalte"等技術(shù)在后臺先拼接成對應的HTML結(jié)構(gòu),然后轉(zhuǎn)換成字符串,在每個對應的路由返回對應的數(shù)據(jù)(文件)即可

Jade模版服務端渲染,代碼實現(xiàn):
const express= require("express")
const app =express()
const jade = require("jade")
const result = ***
const url path = *** 
const html = jade.renderFile(url, { data: result, urlPath })//傳入數(shù)據(jù)給模板引擎
app.get("/",(req,res)=>{
    res.send(html)//直接吐渲染好的`html`文件拼接成字符串返回給客戶端
}) //RestFul接口 

app.listen(3000,err=>{
    //do something
})

使用jQuery等傳統(tǒng)庫繪制的前端頁面

傳統(tǒng)前后端不分離,服務端渲染的優(yōu)缺點: 優(yōu)點:

SEO友好,因為返回給前端的是渲染好的HTML結(jié)構(gòu),里面的內(nèi)容都可以被爬蟲抓取到。

對于一些應用性能等要求不高的項目,比如某個公司的靜態(tài)網(wǎng)頁,內(nèi)容很少的情況下,直接一把梭就好,不用再搭建工程化的環(huán)境等

對于后端程序員(全干工程師)來說,不用去特意學習前端框架,公司也不用特意去招聘前端

兼容性好,傳統(tǒng)服務端渲染多頁面應用吐出來的都是字符串,HTML結(jié)構(gòu)

缺點:

如果項目很大,不利于維護,據(jù)我所知,目前很多云計算公司,還有不少都是使用非單頁面應用,例如一個幾十萬行的項目是用jQuery寫的,如果注釋和文檔不是非常齊全,那么真的會無從下手

性能和用戶體驗,不能跟單頁面應用相比

后期迭代,升級空間不大,目前大部分寫得比較好的庫,都建立vue,react等框架基礎上,他們都有一套自己的運行機制,有自己的生命周期,并且不像傳統(tǒng)的應用,還加上了一層虛擬DOM以及diff算法

現(xiàn)在類似Ant-Design-pro這樣的開箱即用的庫已經(jīng)很多,單頁面應用的學習和開發(fā)成本已經(jīng)很低很低,如果還在使用傳統(tǒng)的技術(shù)去開發(fā)新的應用,對于開發(fā)人員多內(nèi)心來說也是一種折磨。

這里并不是說多頁面應用不好,只能說各有各自的好,單頁面應用如果通過大量的極致優(yōu)化手段,是可以從不少方面跟原生一拼。

目前的單頁面應用:

只有一張Web頁面的應用,是一種從Web服務器加載的富客戶端,單頁面跳轉(zhuǎn)僅刷新局部資源 ,公共資源(js、css等)僅需加載一次,常用于PC端官網(wǎng)、購物等網(wǎng)站

其實只有一個空的DIV標簽,其他都是js動態(tài)生態(tài)的內(nèi)容

單頁面應用實現(xiàn)步驟: 代碼實現(xiàn):

首先是一個靜態(tài)模板文件 index.html





    
    
    
    Document



    

vue react框架的入口文件中指定對應的渲染元素:

import React from "react;
import ReactDOM from "react-dom";

ReactDOM.render(
,
document.querySelector("#root")
)

引入react-router或者 react-router-dom,dva等路由跳轉(zhuǎn)的庫

配置路由跳轉(zhuǎn)

//這里使用HashRouter
      //React錯誤邊界
        
          
          
          //404路由或者重定向都可以
        
      
單頁面應用所謂路由跳轉(zhuǎn),其實最終結(jié)果就是:

瀏覽器的url地址發(fā)生變化,但是其實并沒有發(fā)送請求,也沒有刷新整個頁面

根據(jù)我們配置的路由信息,每次點擊切換路由,會切換到不同的組件顯示,類似于選項卡功能的實現(xiàn),但是同時url地址欄會變化

分為HashRouterBrowserRouter兩種模式

自己實現(xiàn)一個粗略的路由跳轉(zhuǎn): 自己實現(xiàn)傳統(tǒng)的Hash模式跳轉(zhuǎn):
hash 就是指 url 后的 # 號以及后面的字符。例如www.baidu.com/#segmentfault,那么#segmentfault就是hash

需要用到的幾個知識點:

window.location.hash = "**"; // 設置當前的hash值

const hash = window.location.hash 獲取當前的hash值

hash改變會觸發(fā)windowhashchange事件

window.onhashchange=function(e){
    let newURL = e.newURL; // 改變后的新 url地址
    let oldURL = e.oldURL; // 改變前的舊 url地址
}
這里特別注意,hash改變并不會發(fā)送請求
開始實現(xiàn)Hash模式跳轉(zhuǎn): 使用類似發(fā)布訂閱模式的方式,使用ES6的class實現(xiàn):

初始訂閱,每個不同的hash值,對應不同的函數(shù)調(diào)用處理。

class Router {
  constructor() {
    this.routes = {};
    this.currentUrl = "";
  }
  route(path, callback) {
    this.routes[path] = callback || function() {};
  }
  updateView() {
    this.currentUrl = location.hash.slice(1) || "/";
    this.routes[this.currentUrl] && this.routes[this.currentUrl]();
  }
  init() {
    window.addEventListener("load", this.updateView.bind(this), false);
    window.addEventListener("hashchange", this.updateView.bind(this), false);
  }
}

routes 用來存放不同路由對應的回調(diào)函數(shù)

init 用來初始化路由,在 load 事件發(fā)生后刷新頁面,并且綁定 hashchange 事件,當 hash 值改變時觸發(fā)對應回調(diào)函數(shù)

開始使用:


這樣一個簡單的hash模式路由就做好了,剩下的就是路由嵌套,以及錯誤邊界的處理
History模式實現(xiàn):

History來自Html5的規(guī)范

History模式,url地址欄的改變并不會觸發(fā)任何事件

History模式,可以使用history.pushState,history.replaceState來控制url地址,history.pushState() 和 history.replaceState() 的區(qū)別在于:

history.pushState() 在保留現(xiàn)有歷史記錄的同時,將 url 加入到歷史記錄中。

history.replaceState() 會將歷史記錄中的當前頁面歷史替換為 url。

History模式下,刷新頁面會404,需要后端配合匹配一個任意路由,重定向到首頁,特別是加上Nginx反向代理服務器的時候

我們需要換個思路,我們可以羅列出所有可能觸發(fā) history 改變的情況,并且將這些方式一一進行攔截,變相地監(jiān)聽 history 的改變。
對于一個應用而言,url 的改變(不包括 hash 值得改變)只能由下面三種情況引起:

點擊瀏覽器的前進或后退按鈕

點擊 a 標簽

在 JS 代碼中觸發(fā) history.push(replace)State 函數(shù)

只要對上述三種情況進行攔截,就可以變相監(jiān)聽到 history 的改變而做出調(diào)整。針對情況 1,HTML5 規(guī)范中有相應的 onpopstate 事件,通過它可以監(jiān)聽到前進或者后退按鈕的點擊,值得注意的是,調(diào)用 history.push(replace)State 并不會觸發(fā) onpopstate 事件。
開始實現(xiàn):
class Router {
  constructor() {
    this.routes = {};
    this.currentUrl = "";
  }
  route(path, callback) {
    this.routes[path] = callback || function() {};
  }
  updateView(url) {
    this.currentUrl = url;
    this.routes[this.currentUrl] && this.routes[this.currentUrl]();
  }
  bindLink() {
    const allLink = document.querySelectorAll("a[data-href]");
    for (let i = 0, len = allLink.length; i < len; i++) {
      const current = allLink[i];
      current.addEventListener(
        "click",
        e => {
          e.preventDefault();
          const url = current.getAttribute("data-href");
          history.pushState({}, null, url);
          this.updateView(url);
        },
        false
      );
    }
  }
  init() {
    this.bindLink();
    window.addEventListener("popstate", e => {
      this.updateView(window.location.pathname);
    });
    window.addEventListener("load", () => this.updateView("/"), false);
  }
}
Router 跟之前 Hash 路由很像,不同的地方在于:

init 初始化函數(shù),首先需要獲取所有特殊的鏈接標簽,然后監(jiān)聽點擊事件,并阻止其默認事件,觸發(fā) history.pushState 以及更新相應的視圖。

另外綁定 popstate 事件,當用戶點擊前進或者后退的按鈕時候,能夠及時更新視圖,另外當剛進去頁面時也要觸發(fā)一次視圖更新。

實際使用:


跟之前的 html 基本一致,區(qū)別在于用 data-href 來表示要實現(xiàn)軟路由的鏈接標簽。

當然上面還有情況 3,就是你在 JS 直接觸發(fā) pushState 函數(shù),那么這時候你必須要調(diào)用視圖更新函數(shù),否則就是出現(xiàn)視圖內(nèi)容和 url 不一致的情況。

setTimeout(() => {
  history.pushState({}, null, "/about");
  router.updateView("/about");
}, 2000);
React-router-dom源碼: Router組件:
export class Route extends Component {
  componentWillMount() {
    window.addEventListener("hashchange", this.updateView, false);
  }
  componentWillUnmount() {
    window.removeEventListener("hashchange", this.updateView, false);
  }
  updateView = () => {
    this.forceUpdate();
  }
  render() {
    const { path, exact, component } = this.props;
    const match = matchPath(window.location.hash, { exact, path });
    if (!match) {
      return null;
    }
    if (component) {
      return React.createElement(component, { match });
    }
    return null;
  }
}

組件掛載監(jiān)聽hash change原生事件,將要卸載時候移除事件監(jiān)聽防止內(nèi)存泄漏

每次hash改變,就觸發(fā)所有對應hash的回掉,所有的Router都去更新視圖

每個Router組件中,都去對比當前的hash值和這個組件的path屬性,如果不一樣,那么就返回null,·否則就渲染這個組件對應的視圖

History模式的實現(xiàn):

實現(xiàn)History

這里想多留些時間寫其他源碼,這篇文章寫得非常好,大家也可以去看看,本文很多借鑒他的。
withRouter高階函數(shù)的源碼:
var withRouter = function withRouter(Component) {
  var C = function C(props) {
    var wrappedComponentRef = props.wrappedComponentRef,
        remainingProps = _objectWithoutProperties(props, ["wrappedComponentRef"]);

    return _react2.default.createElement(_Route2.default, {
      children: function children(routeComponentProps) {
        return _react2.default.createElement(Component, _extends({}, remainingProps, routeComponentProps, {
          ref: wrappedComponentRef
        }));
      }
    });
  };

  C.displayName = "withRouter(" + (Component.displayName || Component.name) + ")";
  C.WrappedComponent = Component;
  C.propTypes = {
    wrappedComponentRef: _propTypes2.default.func
  };

  return (0, _hoistNonReactStatics2.default)(C, Component);
};

傳入一個組件,返回一個新的組件,并且給這個組件賦予全局屬性,擁有路由組件的三大屬性

Switch組件:
Switch.prototype.render = function render() {
    var route = this.context.router.route;
    var children = this.props.children;

    var location = this.props.location || route.location;

    var match = void 0,
        child = void 0;
    _react2.default.Children.forEach(children, function (element) {
      if (match == null && _react2.default.isValidElement(element)) {
        var _element$props = element.props,
            pathProp = _element$props.path,
            exact = _element$props.exact,
            strict = _element$props.strict,
            sensitive = _element$props.sensitive,
            from = _element$props.from;

        var path = pathProp || from;

        child = element;
        match = (0, _matchPath2.default)(location.pathname, { path: path, exact: exact, strict: strict, sensitive: sensitive }, route.match);
      }
    });

    return match ? _react2.default.cloneElement(child, { location: location, computedMatch: match }) : null;
  };

遍歷所以傳入的子元素

如果有符合的路由對應的元素,那么就返回,而且只匹配這一個路由。不再繼續(xù)往下匹配

如果第二條沒有找到符合的元素,那么拋出錯誤

如果覺得寫得好,記得點個贊哦,另外新建了微信和QQ群,歡迎各位小哥哥小姐姐入駐~

微信群:

QQ群

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/116162.html

相關(guān)文章

  • 徹底理清前端頁面應用SPA實現(xiàn)原理精讀源碼

    showImg(https://segmentfault.com/img/bVbvOmp?w=1612&h=888); 隨著React Vue前端框架的興起,出現(xiàn)了Vue-router,react-router-dom等前端路由管理庫,利用他們構(gòu)建出來的單頁面應用,也是越來越接近原生的體驗,再也不是以前的點擊標簽跳轉(zhuǎn)頁面,刷新整個頁面了,那么他們的原理是什么呢? 優(yōu)質(zhì)gitHub開源練手項目: ...

    xiaodao 評論0 收藏0
  • 徹底理清前端頁面應用SPA實現(xiàn)原理精讀源碼

    showImg(https://segmentfault.com/img/bVbvOmp?w=1612&h=888); 隨著React Vue前端框架的興起,出現(xiàn)了Vue-router,react-router-dom等前端路由管理庫,利用他們構(gòu)建出來的單頁面應用,也是越來越接近原生的體驗,再也不是以前的點擊標簽跳轉(zhuǎn)頁面,刷新整個頁面了,那么他們的原理是什么呢? 優(yōu)質(zhì)gitHub開源練手項目: ...

    sunny5541 評論0 收藏0
  • 前端基礎

    摘要:談起閉包,它可是兩個核心技術(shù)之一異步基于打造前端持續(xù)集成開發(fā)環(huán)境本文將以一個標準的項目為例,完全拋棄傳統(tǒng)的前端項目開發(fā)部署方式,基于容器技術(shù)打造一個精簡的前端持續(xù)集成的開發(fā)環(huán)境。 這一次,徹底弄懂 JavaScript 執(zhí)行機制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機制,如果讀完本文還不懂,可以揍我。 不論你是javascript新手還是老鳥,不論是面試求職,還是日...

    graf 評論0 收藏0
  • 深入了解頁面spa應用(一)

    摘要:一,什么是單頁面應用通俗的來講,就是一個應用只有一個頁面,用戶通過切換路由和動態(tài)獲取數(shù)據(jù)達到頁面更新的目的,整個應用的使用過程中,頁面只是局部刷新。 一, 什么是單頁面應用 通俗的來講,就是一個應用只有一個頁面,用戶通過切換路由和動態(tài)獲取數(shù)據(jù)達到頁面更新的目的,整個應用的使用過程中,頁面只是局部刷新。在整個應用初始加載時,會一次性加載所有靜態(tài)文件或所有公共靜態(tài)文件(切換頁面時,加載相應...

    sugarmo 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<