回顧 knockout + require + director 構建單頁面程序

目前react,vue,angular等框架都支持單頁面程序,最近在回顧一些知識,想起剛畢業的時候接觸到knockout + require + director 構建的單頁面程序。因此寫一篇文章回顧一下之前的單頁面,或許對於如今瞭解單頁面程序有一些幫助。 ​javascript

前置簡要說明

從之前的多頁面程序,在只有js,html,jquery的年代,路由的跳轉經過連接跳轉,每一個頁面的header和footer會在每個頁面書寫一遍。那想建立一個單頁面程序。會有幾個問題?css


  • 動態渲染部分的區域內容須要能夠經過js進行控制
  • 監控路由的跳轉
  • js控制動態路由的跳轉渲染具體的頁面,那html的渲染和js的執行如何處理,由於這裏咱們但願經過ko來實行一個頁面和數據model的綁定,因此這裏還須要添加上ko綁定頁面數據data的處理,因此對於js文件的處理會劃分紅相似react / vue 生命週期的方式來處理

rquire

require.js的誕生,就是爲了兩個問題:html

  1. 實現js文件的異步加載,避免網頁失去響應;
  2. 管理模塊之間的依賴性,便於代碼的編寫和維護。

咱們這裏使用require是爲了經過require來進行html和js的文件控制,require生來是用來控制js的,那麼怎麼控制html呢,能夠經過require的插件text進行html控制。vue

knockout

knockout是mvvm框架的鼻祖了。實現了雙向數據綁定的功能。 ​java

Knockout是一款很優秀的JavaScript庫,它能夠幫助你僅使用一個清晰整潔的底層數據模型(data model)便可建立一個富文本且具備良好的顯示和編輯功能的用戶界面。react

任什麼時候候你的局部UI內容須要自動更新(好比:依賴於用戶行爲的改變或者外部的數據源發生變化),KO均可以很簡單的幫你實現,而且很是易於維護。 ​jquery

咱們這裏使用的主要是knockout tempate的功能以及業務功能書寫的時候用的ko的數據綁定功能。template綁定經過模板將數據render到頁面。模板綁定對於構建嵌套結構的頁面很是方便。 ​git

注圖片來源:www.cnblogs.com/tangge/p/10…github

director

director.js 就是客戶端的路由註冊/解析器,它在不刷新頁面的狀況下,利用「#」符號組織不一樣的URL路徑,並根據不一樣的URL路徑來匹配不一樣的回調方法。通俗點說就是什麼樣的路徑幹什麼樣的事情。web

這裏用director主要是爲了作單頁面路由的切換調用js代碼。 ​

require

上面已經說了,咱們須要經過require來進行對html,css的控制。 ​

初始化require

<script src="js/require.js" data-main="js/main"></script>
複製代碼

data-main屬性的做用是,指定網頁程序的主模塊。在上例中,就是js目錄下面的main.js,這個文件會第一個被require.js加載。因爲require.js默認的文件後綴名是js,因此能夠把main.js簡寫成main。 ​

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <main id="main"></main>
  <script data-main="/Scripts/framework/main" src="/Scripts/lib/require.js"></script>
</body>
</html>
複製代碼

咱們的代碼裏面,加載require文件以後會執行main.js文件。 ​

main.js文件

var paths = {
/* TODO: register all AMD modules by providing CamelCase aliases, exceptions are RequireJS plugins and named AMD modules, whose names are fixed */
/* follow files dictionary order */
'jquery': 'Scripts/lib/jquery',
'Routes': 'Scripts/framework/routes',
'knockout': 'Scripts/lib/knockout',


//framework
'Router': 'Scripts/lib/director',
'WebPageContrl': 'Scripts/framework/webPageContrl',
'AppRouter': 'Scripts/framework/router',
'Error-js': 'Scripts/app/Error',
'Error-html': 'templates/Error-html.html',
"knockout-amd-helpers": "Scripts/lib/knockout-amd-helpers",
"text": "Scripts/lib/text",

//bootstrap
'Custom': 'Scripts/lib/custom',
'Bootstrap': 'Scripts/lib/bootstrap.min',

//Customer
'CustomerIntroduction-html': 'templates/customer/CustomerIntroduction.html',
'CustomerIntroduction-js': 'Scripts/app/customer/CustomerIntroduction',

 //require
'RequireIntroduction-html': 'templates/require/RequireIntroduction.html',
"RequireIntroduction-js": 'Scripts/app/require/RequireIntroduction',
'RequireCode-html': 'templates/require/RequireCode.html',
"RequireCode-js": 'Scripts/app/require/RequireCode',

//Javascript
'UnknowJavascriptSecond-html': 'templates/javascript/UnknowJavascriptSecond.html',
'UnknowJavascriptSecond-js': 'Scripts/app/javascript/UnknowJavascriptSecond',
};

var baseUrl = '/';

require.config({
	baseUrl: baseUrl,
	paths: paths,

	shim: {
		/* TODO: provide all needed shims for non-AMD modules */
		'Router': {
			exports: 'Router'
		},
		'Custom': {
      exports: 'Custom'
		},
		'Custom': ['Bootstrap'],
		'Bootstrap': ['jquery']
	}
});

require(["jquery", "RequireIntroduction-js", "text!RequireIntroduction-html"],
    function ($, module, html) {
        console.log("Start test require html!");
        $('#main').html(html);
        console.log("Start test require js!");
        module.TestRequireJs();
    }
);
複製代碼

這個文件可能有點多了一點其餘的,因此這裏咱們還只看重點就能夠了。 ​

require.config

使用require.config()方法,咱們能夠對模塊的加載行爲進行自定義。

require.config()就寫在主模塊(main.js)的頭部。參數就是一個對象,這個對象的paths屬性指定各個模塊的加載路徑。 ​

咱們這裏將不少長文件路徑的文件經過別名定義。 ​

渲染html & 執行 js

require(["jquery", "RequireIntroduction-js", "text!RequireIntroduction-html"],
    function ($, module, html) {
        console.log("Start test require html!");
        $('#main').html(html);
        console.log("Start test require js!");
        module.TestRequireJs();
    }
);
複製代碼

這裏在獲取到當前頁面RequireIntroduction的html和js文件。經過jquery的html方法渲染html。獲取js模塊,調用js方法。 ​

看一下html和js的代碼

<!--RequireIntroduction-html-->
<div>
  require introduction
</div>
複製代碼
// RequireIntroduction-js
define(function () {
    function RequireIntroductionViewModel() {
        var self = this;
        
        self.TestRequireJs = () => {
            console.log('testRequireJS');
          }
    }

    return new RequireIntroductionViewModel();
});
複製代碼

knockout

下載所須要的文件knockout.js 和knockout-amd-helpers.js,knockout-amd-helpers.js在本章節中主要的做用在於knockout在渲染模板時能夠直接渲染整個html文件,而不用在當前web頁面中定義模板。 ​

如今咱們想經過knockout template進行頁面的渲染。 ​

修改index.html

<main id="main" data-bind="template: { name: 'RequireIntroduction-html', data: require('RequireIntroduction-js').data, afterRender: require('RequireIntroduction-js').afterRender }">
  </main>
  <script data-main="/Scripts/framework/main" src="/Scripts/lib/require.js"></script>
複製代碼

修改main.js

var paths = {
/* TODO: register all AMD modules by providing CamelCase aliases, exceptions are RequireJS plugins and named AMD modules, whose names are fixed */
/* follow files dictionary order */
'jquery': 'Scripts/lib/jquery',
'Routes': 'Scripts/framework/routes',
'knockout': 'Scripts/lib/knockout',


//framework
'Router': 'Scripts/lib/director',
'WebPageContrl': 'Scripts/framework/webPageContrl',
'AppRouter': 'Scripts/framework/router',
'Error-js': 'Scripts/app/Error',
'Error-html': 'templates/Error-html.html',
"knockout-amd-helpers": "Scripts/lib/knockout-amd-helpers",
"text": "Scripts/lib/text",

//bootstrap
'Custom': 'Scripts/lib/custom',
'Bootstrap': 'Scripts/lib/bootstrap.min',

//Customer
'CustomerIntroduction-html': 'templates/customer/CustomerIntroduction.html',
'CustomerIntroduction-js': 'Scripts/app/customer/CustomerIntroduction',

 //require
'RequireIntroduction-html': 'templates/require/RequireIntroduction.html',
"RequireIntroduction-js": 'Scripts/app/require/RequireIntroduction',
'RequireCode-html': 'templates/require/RequireCode.html',
"RequireCode-js": 'Scripts/app/require/RequireCode',

//Javascript
'UnknowJavascriptSecond-html': 'templates/javascript/UnknowJavascriptSecond.html',
'UnknowJavascriptSecond-js': 'Scripts/app/javascript/UnknowJavascriptSecond',
};

var baseUrl = '/';

require.config({
	baseUrl: baseUrl,
	paths: paths,

	shim: {
		/* TODO: provide all needed shims for non-AMD modules */
		'Router': {
			exports: 'Router'
		},
		'Custom': {
      exports: 'Custom'
		},
		'Custom': ['Bootstrap'],
		'Bootstrap': ['jquery']
	}
});


require(["knockout", "RequireIntroduction-js", "knockout-amd-helpers", "text"], function (ko, RequireIntroduction) {
	ko.bindingHandlers.module.baseDir = "modules";

	//fruits/vegetable modules have embedded template
	ko.bindingHandlers.module.templateProperty = "embeddedTemplate";
	ko.applyBindings(RequireIntroduction);
});
複製代碼

從簡要說明的時候咱們知道,須要用knockout的template來進行html渲染,可是這裏咱們沒有去定義具體的template,而是使用下面的代碼:

require(["knockout", "RequireIntroduction-js", "knockout-amd-helpers", "text"], function (ko, RequireIntroduction) {
	ko.bindingHandlers.module.baseDir = "modules";

	//fruits/vegetable modules have embedded template
	ko.bindingHandlers.module.templateProperty = "embeddedTemplate";
	ko.applyBindings(RequireIntroduction);
});
複製代碼

由於這裏採用了knockout-amd-helpers ​

該插件旨在成爲在 Knockout.js 中使用 AMD 模塊的輕量級且靈活的解決方案。這個庫最初設計用於require.js或curl.js。它提供了兩個主要功能:

  1. 加強默認模板引擎以容許它使用 AMD 加載器的文本插件加載外部模板。這使您能夠在單獨的 HTML 文件中建立模板並根據須要將它們拉入(理想狀況下,在生產中模板包含在優化文件中)。
  2. 建立一個module綁定,它提供了一種從 AMD 模塊加載數據的靈活方法,並將其綁定到外部模板、匿名/內聯模板或模塊自己的屬性中定義的模板。

上面的代碼執行的效果是什麼呢?

咱們書寫代碼的時候,如此調用就能夠從require當中加載當前key對應的html文件。

走到這裏,require已經和knockout聯繫在一塊兒。knockout進行模版渲染,require進行文件控制。

頁面渲染

回顧一下前面的問題

接下來主要的問題是: 監控路由變化,在系統代碼裏能夠動態處理當前路徑對應的html / js資源。而後對於js代碼的執行進行ko的綁定和生命週期方式的拆分。 ​

集成director完成單頁面程序

系統層級代碼的處理 webPageContrl

/* * @Description: * @Author: rodchen * @Date: 2021-07-24 12:08:10 * @LastEditTime: 2021-07-24 21:33:46 * @LastEditors: rodchen */
define(['knockout', 'jquery', 'Router', 'Custom'], function (ko, $, Router) {
	var initialRun = true;

	function isEndSharp() { // url end with #
		if (app.lastUrl != "" && location.toLocaleString().indexOf(app.lastUrl) != -1 && location.toLocaleString().indexOf('#') != -1 && location.hash == "") {
			return true;
		}

		return false;
	}

	var app = {
    // 每次路由切換,調用當前方法
	  initJS: function (pageName) {
			require([pageName + '-js'], function (page) {
					app.init(pageName, page);
			});
	  },
		init: function(pageName, pageData) {
			if (isEndSharp()) {
				return;
			}

      // js模塊的init方法執行
			pageData.init(); 

      // ko綁定的數據,數據綁定的數據源是js模塊裏的data模塊
			app.page({
			    name: pageName,				// 路由對應的page標識
			    data: pageData
			}); 

      // 初次執行,ko綁定
			if (initialRun) {
				ko.applyBindings(app, document.getElementsByTagName('html')[0]); 
				initialRun = false;
			}
		},
		page: ko.observable({
			name: '', 
			data: {
				init: function() {}
			}
		}),
		// knockout template 加載成功以後的回掉函數
		afterRender: function() {
			if (app.page().data.afterRender) {
				app.page().data.afterRender();
			}
		}
	};

	return app;
});
複製代碼

director 處理

/* * // router.js * @Description: * @Author: rodchen * @Date: 2021-07-24 12:08:10 * @LastEditTime: 2021-07-24 19:59:11 * @LastEditors: rodchen */
define(['WebPageContrl', 'Routes', 'Router'], function (WebPageContrl, Routes, Router) {
	var routes = {};

	$.each(Routes, function(key, value) {
		var values = value.split(' ');
		var pageName = values[0];
    
		routes[key] = function() {
		  WebPageContrl.initJS(pageName);  // 經過director路由的回掉函數,而後執行系統文件的initJS方法
		};
	});

	var router = new Router(routes).configure({
		notfound: function() {
			routes['/error404/:code'](404);
		}
	});

	var urlNotAtRoot = window.location.pathname && (window.location.pathname != '/');

	if (urlNotAtRoot) {
		router.init();
	} else {
		router.init('/');
	}

	return router;
});
複製代碼
// Routes

define({
	'/error404/:code': 'Error /',
	'/': 'CustomerIntroduction /',
	'': 'CustomerIntroduction /',

    //Customer
	'/CustomerIntroduction': 'CustomerIntroduction /',

    //Require
	'/RequireIntroduction': 'RequireIntroduction /',
	'/RequireCode': 'RequireCode /',

    //Javascript
	'/UnknowJavascriptSecond': 'UnknowJavascriptSecond /'
})
複製代碼

串一個整個流程來講明

代碼地址:github.com/rodchen-kin…

// 代碼執行 經過http-server執行,由於是相對路徑,須要掛靠在服務 

npm install global http-server
複製代碼

屏幕錄製2021-07-24 下午9.42.49.2021-07-24 21_45_27.gif

參照

相關文章
相關標籤/搜索