摘要:又從開始學習新的東西了,想著還是記錄一下學習歷程,有輸入就要有輸出吧,免得以后給忘記學了些什么框架與主流工具的整合地址首先,項目,學習里面的。如有錯誤和問題歡迎各位大佬不吝賜教輕量級框架與主流工具的整合二完善與優化
前言
老大說以后會用 next 來做一下 SSR 的項目,讓我們有空先學學。又從 0 開始學習新的東西了,想著還是記錄一下學習歷程,有輸入就要有輸出吧,免得以后給忘記學了些什么~
Next框架與主流工具的整合github地址:https://github.com/code-coder/next-mobile-complete-app
?? 數據層:redux + saga
?? 視圖層:sass + postcss
?? 服務端:koa
做一個項目就像造一所房子,最開始就是“打地基”: 1. 新建了一個項目,用的是這里面的一個with-redux-saga的template 戳這里。 2. 添加sass和postcss,參考的是 這里新建next.config.js,復制以下代碼:
const withSass = require("@zeit/next-sass"); module.exports = withSass({ postcssLoaderOptions: { parser: true, config: { ctx: { theme: JSON.stringify(process.env.REACT_APP_THEME) } } } });
新建postcss.config.js,復制以下代碼:
module.exports = { plugins: { autoprefixer: {} } };
在package.js添加自定義browserList,這個就根據需求來設置了,這里主要是移動端的。
// package.json "browserslist": [ "IOS >= 8", "Android > 4.4" ],
順便說一下browserlist某些配置會報錯,比如直接填上默認配置
"browserslist": [ "last 1 version", "> 1%", "maintained node versions", "not dead" ] // 會報以下錯誤 Unknown error from PostCSS plugin. Your current PostCSS version is 6.0.23, but autoprefixer uses 5.2.18. Perhaps this is the source of the error below.3. 配置koa,參照custom-server-koa
新建server.js文件,復制以下代碼:
const Koa = require("koa"); const next = require("next"); const Router = require("koa-router"); const port = parseInt(process.env.PORT, 10) || 3000; const dev = process.env.NODE_ENV !== "production"; const app = next({ dev }); const handle = app.getRequestHandler(); app.prepare().then(() => { const server = new Koa(); const router = new Router(); router.get("*", async ctx => { await handle(ctx.req, ctx.res); ctx.respond = false; }); server.use(async (ctx, next) => { ctx.res.statusCode = 200; await next(); }); server.use(router.routes()); server.listen(port, () => { console.log(`> Ready on http://localhost:${port}`); }); });
然后在配置一下package.json的scripts
"scripts": { "dev": "node server.js", "build": "next build", "start": "NODE_ENV=production node server.js" }現在只是把地基打好了,接著需要完成排水管道、鋼筋架構等鋪設:
?? 調整項目結構
?? layout布局設計
?? 請求攔截、loading狀態及錯誤處理
1. 調整后的項目結構-- components -- pages ++ server || -- server.js -- static ++ store || ++ actions || -- index.js || ++ reducers || -- index.js || ++ sagas || -- index.js -- styles -- next.config.js -- package.json -- postcss.config.js -- README.md2. layout布局設計。
ant design 是我使用過而且比較有好感的UI框架。既然這是移動端的項目,ant design mobile 成了首選的框架。我也看了其他的主流UI框架,現在流行的UI框架有Amaze UI、Mint UI、Frozen UI等等,個人還是比較喜歡ant出品的。
恰好templates中有ant design mobile的demo:with-ant-design-mobile。
基于上面的項目結構整合with-ant-design-mobile這個demo。
新增babel的配置文件:.babelrc 添加以下代碼:
{ "presets": ["next/babel"], "plugins": [ [ "import", { "libraryName": "antd-mobile" } ] ] }
修改next.config.js為:
const withSass = require("@zeit/next-sass"); const path = require("path"); const fs = require("fs"); const requireHacker = require("require-hacker"); function setupRequireHacker() { const webjs = ".web.js"; const webModules = ["antd-mobile", "rmc-picker"].map(m => path.join("node_modules", m)); requireHacker.hook("js", filename => { if (filename.endsWith(webjs) || webModules.every(p => !filename.includes(p))) return; const webFilename = filename.replace(/.js$/, webjs); if (!fs.existsSync(webFilename)) return; return fs.readFileSync(webFilename, { encoding: "utf8" }); }); requireHacker.hook("svg", filename => { return requireHacker.to_javascript_module_source(`#${path.parse(filename).name}`); }); } setupRequireHacker(); function moduleDir(m) { return path.dirname(require.resolve(`${m}/package.json`)); } module.exports = withSass({ webpack: (config, { dev }) => { config.resolve.extensions = [".web.js", ".js", ".json"]; config.module.rules.push( { test: /.(svg)$/i, loader: "emit-file-loader", options: { name: "dist/[path][name].[ext]" }, include: [moduleDir("antd-mobile"), __dirname] }, { test: /.(svg)$/i, loader: "svg-sprite-loader", include: [moduleDir("antd-mobile"), __dirname] } ); return config; } });
static新增rem.js
(function(doc, win) { var docEl = doc.documentElement, // isIOS = navigator.userAgent.match(/(i[^;]+;( U;)? CPU.+Mac OS X/), // dpr = isIOS ? Math.min(win.devicePixelRatio, 3) : 1; // dpr = window.top === window.self ? dpr : 1; //被iframe引用時,禁止縮放 dpr = 1; var scale = 1 / dpr, resizeEvt = "orientationchange" in window ? "orientationchange" : "resize"; docEl.dataset.dpr = dpr; var metaEl = doc.createElement("meta"); metaEl.name = "viewport"; metaEl.content = "initial-scale=" + scale + ",maximum-scale=" + scale + ", minimum-scale=" + scale + ",user-scalable=no"; docEl.firstElementChild.appendChild(metaEl); var recalc = function() { var width = docEl.clientWidth; // 大于1280按1280來算 if (width / dpr > 1280) { width = 1280 * dpr; } // 乘以100,px : rem = 100 : 1 docEl.style.fontSize = 100 * (width / 375) + "px"; doc.body && doc.body.style.height !== docEl.clientHeight && docEl.clientHeight > 360 && (doc.body.style.height = docEl.clientHeight + "px"); }; recalc(); if (!doc.addEventListener) return; win.addEventListener(resizeEvt, recalc, false); win.onload = () => { doc.body.style.height = docEl.clientHeight + "px"; }; })(document, window);
增加移動端設備及微信瀏覽器的判斷
(function() { // 判斷移動PC端瀏覽器和微信端瀏覽器 var ua = navigator.userAgent; // var ipad = ua.match(/(iPad).* OSs([d _] +)/); var isAndroid = ua.indexOf("Android") > -1 || ua.indexOf("Adr") > -1; // android var isIOS = !!ua.match(/(i[^;]+;( U;)? CPU.+Mac OS X/); // ios if (/(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent)) { window.isAndroid = isAndroid; window.isIOS = isIOS; window.isMobile = true; } else { // 電腦PC端判斷 window.isDeskTop = true; } ua = window.navigator.userAgent.toLowerCase(); if (ua.match(/MicroMessenger/i) == "micromessenger") { window.isWeChatBrowser = true; } })();
_document.js新增引用:
構造布局
在components文件夾新增layout和tabs文件夾
++ components || ++ layout || || -- Layout.js || || -- NavBar.js || ++ tabs || || -- TabHome.js || || -- TabIcon.js || || -- TabTrick.js || || -- Tabs.js
應用頁面大致結構是(意思一下)
首頁
nav | |
---|---|
content | |
tabs |
其他頁
nav | |
---|---|
content |
最后,使用redux管理nav的title,使用router管理后退的箭頭
// other.js static getInitialProps({ ctx }) { const { store, req } = ctx; // 通過這個action改變導航欄的標題 store.dispatch(setNav({ navTitle: "Other" })); const language = req ? req.headers["accept-language"] : navigator.language; return { language }; }
// NavBar.js componentDidMount() { // 通過監聽route事件,判斷是否顯示返回箭頭 Router.router.events.on("routeChangeComplete", this.handleRouteChange); } handleRouteChange = url => { if (window && window.history.length > 0) { !this.setState.canGoBack && this.setState({ canGoBack: true }); } else { this.setState.canGoBack && this.setState({ canGoBack: false }); } };
// NavBar.js let onLeftClick = () => { if (this.state.canGoBack) { // 返回上級頁面 window.history.back(); } };3、請求攔截、loading及錯誤處理
封裝fetch請求,使用單例模式對請求增加全局loading等處理。
要點:1、單例模式。2、延遲loading。3、server端渲染時不能加載loading,因為loading是通過document對象操作的
import { Toast } from "antd-mobile"; import "isomorphic-unfetch"; import Router from "next/router"; // 請求超時時間設置 const REQUEST_TIEM_OUT = 10 * 1000; // loading延遲時間設置 const LOADING_TIME_OUT = 1000; class ProxyFetch { constructor() { this.fetchInstance = null; this.headers = { "Content-Type": "application/json" }; this.init = { credentials: "include", mode: "cors" }; // 處理loading this.requestCount = 0; this.isLoading = false; this.loadingTimer = null; } /** * 請求1s內沒有響應顯示loading */ showLoading() { if (this.requestCount === 0) { this.loadingTimer = setTimeout(() => { Toast.loading("加載中...", 0); this.isLoading = true; this.loadingTimer = null; }, LOADING_TIME_OUT); } this.requestCount++; } hideLoading() { this.requestCount--; if (this.requestCount === 0) { if (this.loadingTimer) { clearTimeout(this.loadingTimer); this.loadingTimer = null; } if (this.isLoading) { this.isLoading = false; Toast.hide(); } } } /** * 獲取proxyFetch單例對象 */ static getInstance() { if (!this.fetchInstance) { this.fetchInstance = new ProxyFetch(); } return this.fetchInstance; } /** * get請求 * @param {String} url * @param {Object} params * @param {Object} settings: { isServer, noLoading, cookies } */ async get(url, params = {}, settings = {}) { const options = { method: "GET" }; if (params) { let paramsArray = []; // encodeURIComponent Object.keys(params).forEach(key => { if (params[key] instanceof Array) { const value = params[key].map(item => """ + item + """); paramsArray.push(key + "=[" + value.join(",") + "]"); } else { paramsArray.push(key + "=" + params[key]); } }); if (url.search(/?/) === -1) { url += "?" + paramsArray.join("&"); } else { url += "&" + paramsArray.join("&"); } } return await this.dofetch(url, options, settings); } /** * post請求 * @param {String} url * @param {Object} params * @param {Object} settings: { isServer, noLoading, cookies } */ async post(url, params = {}, settings = {}) { const options = { method: "POST" }; options.body = JSON.stringify(params); return await this.dofetch(url, options, settings); } /** * fetch主函數 * @param {*} url * @param {*} options * @param {Object} settings: { isServer, noLoading, cookies } */ dofetch(url, options, settings = {}) { const { isServer, noLoading, cookies = {} } = settings; let loginCondition = false; if (isServer) { this.headers.cookies = "cookie_name=" + cookies["cookie_name"]; } if (!isServer && !noLoading) { loginCondition = Router.route.indexOf("/login") === -1; this.showLoading(); } const prefix = isServer ? process.env.BACKEND_URL_SERVER_SIDE : process.env.BACKEND_URL; return Promise.race([ fetch(prefix + url, { headers: this.headers, ...this.init, ...options }), new Promise((resolve, reject) => { setTimeout(() => reject(new Error("request timeout")), REQUEST_TIEM_OUT); }) ]) .then(response => { !isServer && !noLoading && this.hideLoading(); if (response.status === 500) { throw new Error("服務器內部錯誤"); } else if (response.status === 404) { throw new Error("請求地址未找到"); } else if (response.status === 401) { if (loginCondition) { Router.push("/login?directBack=true"); } throw new Error("請先登錄"); } else if (response.status === 400) { throw new Error("請求參數錯誤"); } else if (response.status === 204) { return { success: true }; } else { return response && response.json(); } }) .catch(e => { if (!isServer && !noLoading) { this.hideLoading(); Toast.info(e.message); } return { success: false, statusText: e.message }; }); } } export default ProxyFetch.getInstance();寫在最后
一個完整項目的雛形大致出來了,但是還是需要在實踐中不斷打磨和優化。
如有錯誤和問題歡迎各位大佬不吝賜教 :)
Next輕量級框架與主流工具的整合(二)—— 完善與優化
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/97722.html
摘要:從概念來說,就是設備的物理像素與設備獨立像素也就是邏輯像素,以下就稱為邏輯像素的比率。通過這個標簽,我們可以實現初始縮放,就可以達到的邏輯像素眼睛在設備上看起來的,換句話說可以在上充滿豎屏的整個寬度。 前言:18年12月24日項目成功上線了,在經歷了兩周的線上bug、UI以及代碼優化后,解決了不少問題,于是再完善與優化一下這個項目。 布局優化 高清配置 antd-mobile 自定義...
摘要:在,是當之無愧的王者,贏得了與之間的戰爭,攻陷了的城池。于月發布了版本,這一版本為了更好的表現加入了渲染方式。前端框架這個前端框架清單可能是年疲勞的元兇之一。的創建者,目前在工作為尋找構建簡單性和自主配置性之間的平衡做了很大的貢獻。 春節后的第一篇就從這個開始吧~本文已在前端早讀課公眾號上首發 原文鏈接 JavasScript社區在創新的道路上開足了馬力,曾經流行過的也許一個月之后就過...
閱讀 1958·2021-11-15 17:58
閱讀 2139·2021-10-19 11:45
閱讀 3505·2021-09-02 15:40
閱讀 2605·2021-07-25 10:50
閱讀 3755·2019-08-30 15:56
閱讀 3157·2019-08-30 12:44
閱讀 1038·2019-08-26 13:38
閱讀 1881·2019-08-23 18:29