摘要:又从开始学习新的东西了,想着还是记录一下学习历程,有输入就要有输出吧,免得以后给忘记学了些什么框架与主流工具的整合地址首先,项目,学习里面的。如有错误和问题欢迎各位大佬不吝赐教轻量级框架与主流工具的整合二完善与优化
前言
老大说以后会用 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轻量级框架与主流工具的整合(二)—— 完善与优化
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/97722.html
摘要:从概念来说,就是设备的物理像素与设备独立像素也就是逻辑像素,以下就称为逻辑像素的比率。通过这个标签,我们可以实现初始缩放,就可以达到的逻辑像素眼睛在设备上看起来的,换句话说可以在上充满竖屏的整个宽度。 前言:18年12月24日项目成功上线了,在经历了两周的线上bug、UI以及代码优化后,解决了不少问题,于是再完善与优化一下这个项目。 布局优化 高清配置 antd-mobile 自定义...
摘要:在,是当之无愧的王者,赢得了与之间的战争,攻陷了的城池。于月发布了版本,这一版本为了更好的表现加入了渲染方式。前端框架这个前端框架清单可能是年疲劳的元凶之一。的创建者,目前在工作为寻找构建简单性和自主配置性之间的平衡做了很大的贡献。 春节后的第一篇就从这个开始吧~本文已在前端早读课公众号上首发 原文链接 JavasScript社区在创新的道路上开足了马力,曾经流行过的也许一个月之后就过...
阅读 1907·2021-11-15 17:58
阅读 2111·2021-10-19 11:45
阅读 3438·2021-09-02 15:40
阅读 2573·2021-07-25 10:50
阅读 3700·2019-08-30 15:56
阅读 3128·2019-08-30 12:44
阅读 1010·2019-08-26 13:38
阅读 1851·2019-08-23 18:29