移动端适配方案

媒体查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@media screen and (max-width: 960px) {
body {
background-color: #ff6699;
}
}

@media screen and (max-width: 768px) {
body {
background-color: #00ff66;
}
}

@media screen and (max-width: 550px) {
body {
background-color: #6633ff;
}
}

@media screen and (max-width: 320px) {
body {
background-color: #ffff00;
}
}

百分比布局

难统一,使用较少

  • 计算困难,如果我们要定义一个元素的宽度和高度,按照设计稿,必须换算成百分比单位。
  • 各个属性中如果使用百分比,相对父元素的属性并不是唯一的。比如 width 和 height 相对于父元素的 width 和 height,而 margin、padding 不管垂直还是水平方向都相对比父元素的宽度、border-radius 则是相对于元素自身等等,造成我们使用百分比单位容易使布局问题变得复杂。

flex 弹性盒子布局

rem + 动态 font-size

针对不同的屏幕,设置 html 不同的 font-size

  • 媒体查询:通过媒体查询来设置不同尺寸范围内的屏幕 html 的 font-size 尺寸,有以下缺点
    • 需要针对不同的屏编写大量的媒体查询
    • 如果动态改变尺寸,不会实时的进行更新
  • 用 js 动态获取设备宽度:根据 html 的宽度计算出 font-size 的大小,并且设置到 html 上;监听页面的实时改变,并且重新设置 font-size 的大小到 html 上
  • 利用第三方库 lib-flexible 动态 font-size
1
2
3
4
5
6
7
<style>
@media screen and (min-width:320px) {
html{
font-size: 20px;
}
}
</style>

将原来要设置的尺寸,转化成 rem 单位

  • less 的混合 scss 的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
.pxToRem(@px) {
result: 1rem * (@px / 37.5);
}

.box {
width: .pxToRem(100)[result];
height: .pxToRem(100)[result];
background-color: orange;
}

p {
font-size: .pxToRem(14)[result];
}
  • postcss-pxtorem
1
2
3
4
5
6
7
8
9
// postcss.config.js
module.exports = {
plugins: {
"postcss-pxtorem": {
rootValue: 37.5,
propList: ["*"],
},
},
};

VSCode 插件: px to rem

Viewport 单位

vh、vw,相对于视口的单位,对比于 rem 的优点:

  • 用去计算 html 的 font-size 大小,也不需要给 html 设置这样一个 font-size
  • 不会因为设置 html 的 font-size 大小,而必须给 body 再设置一个 font-size,防止继承
  • 因为不依赖 font-size 的尺寸,所以不用担心某些原因 html 的 font-size 尺寸被篡改,页面尺寸混乱
  • vw 相比于 rem 更加语义化,1vw 刚好是 1/100 的 viewport 的大小
  • 可以具备 rem 之前所有的优点
  • less/scss 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@vwUnit:3.75;

.pxToVw(@px) {
result: 1vw * (@px / @vwUnit);
}

.box {
width: .pxToVw(100)[result];
height: .pxToVw(100)[result];
background-color: orange;
}

p {
font-size: .pxToVw(14)[result];
}
  • postcss-px-to-viewport
1
2
3
4
5
6
7
8
// postcss.config.js
module.exports = {
plugins: {
"postcss-px-to-viewport": {
viewportWidth: 375,
},
},
};

Css 预处理器的 mixin 和函数

JSBridge

定义

  1. 以 JavaScript 引擎或 Webview 容器作为媒介,通过协定协议进行通信,实现 Native 端和 Web 端双向通信的一种机制
  2. 双向通信的通道
    • JS 向 Native 发送消息: 调用相关功能、通知 Native 当前 JS 的相关状态等
    • Native 向 JS 发送消息: 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等

Webview

  1. WebView 是移动端提供的运行 JavaScript 的环境,是系统渲染 Web 网页的一个控件,可与页面 JavaScript 交互,实现混合开发
  2. WebView 是手机中内置了一款高性能 Webkit 内核浏览器,在 SDK 中封装的一个组件。不过没有提供地址栏和导航栏,只是单纯的展示一个网页界面。
  3. WebView 可以简单理解为页面里的 iframe 。原生 app 与 WebView 的交互可以简单看作是页面与页面内 iframe 页面进行的交互。就如页面与页面内的 iframe 共用一个 Window 一样,原生与 WebView 也共用了一套原生的方法

  • webview 加载 url 过程:webview 存在一个初始化的过程。为了提升 init 时间,通常做法是 app 启动时初始化一个隐藏的 webview 等待使用,当用户点击需要加载 URL,直接使用这个 webview 来加载,从而减少 webview init 初始化时间。弊端就是带来了额外的内存开销

JSBridge 运行原理

目前主流的 JSBridge 实现中,都是通过拦截 URL 请求来达到 native 端和 webview 端相互通信的效果

主要流程

  1. 在 webview 侧和 native 侧分别注册 bridge,其实就是用一个对象把所有函数储存起来
  2. 在 webview 里面注入初始化代码:
    • 创建一个名为 WVJBCallbacks 的数组,将传入的 callback 参数放到数组内
    • 创建一个 iframe,设置不可见,设置 src 为 https://__bridge_loaded__
    • 设置定时器移除这个 iframe
  3. 在 native 端监听 url 请求:
    • 拦截了所有的 URL 请求并拿到 url
    • 首先判断 isWebViewJavascriptBridgeURL,判断这个 url 是不是 webview 的 iframe 触发的,具体可以通过 host 去判断
    • 继续判断,如果是 isBridgeLoadedURL,那么会执行 injectJavascriptFile 方法,会向 webview 中再次注入一些逻辑,其中最重要的逻辑就是,在 window 对象上挂载一些全局变量和 WebViewJavascriptBridge 属性
    • 继续判断,如果是 isQueueMessageURL,那么这就是个处理消息的回调,需要执行一些消息处理的方法

webview 调用 native 能力

  1. native 端注册 JsBridge
  2. webview 侧创建 iframe,设置 src 为__bridge_load__
  3. native 端捕获请求,注入 jsb 初始化代码,在 window 上挂载相关对象和方法
  4. webview 侧调用 callHandler 方法,并在 responseCallback 上添加 callbackId: responseCallback,并修改 iframe 的 src,触发捕获
  5. native 收到 message,生成一个 responseCallback,并执行 native 侧注册好的方法
  6. native 执行完毕后,通过 webview 执行 _handleMessageFromObjC 方法,取出 callback 函数,并执行

native 调用 webview 能力

native 可以直接调用 webview 注册的 JsBridge 方法,不需要通过触发 iframe 的 src 触发执行

  1. native 侧调用 callHandler 方法,并在 responseCallback 上添加 callbackId: responseCallback
  2. native 侧主动调用 _handleMessageFromObjC 方法,在 webview 中执行对应的逻辑
  3. webview 侧执行结束后,生成带有 responseId 的 message,添加到 sendMessageQueue 中,并修改 iframe 的 src 为 __wvjb_queue_message__
  4. native 端拦截到 url 变化,调用 webview 的逻辑获取到 message,拿到 responseId,并执行对应的 callback 函数

React Native

常见问题

RN 相对于原生 IOS、Android 有哪些优势

  • 性能方面媲美原生 App
  • 绝大部分代码同时适用 IOS/Android,一套代码两系统适用
  • 使用 Javascript 编码,上手容易
  • 组件式开发,易于管理维护,代码复用率高
  • 代码更改后会自动刷新,节省等待时间
  • 支持热更新,更新无需重新安装 App

调用 setState 之后发生了什么

  1. 将传入的参数对象与当前的状态合并,然后触发调和过程
  2. 在调和过程中 react 会根据新的状态以相对高效的方式构建 react 元素树
  3. react 会对新旧元素树进行 diff 算法计算出差异,然后根据差异进行最小化渲染

JS 如何与原生相互调用

  1. JS 调用原生方法
    • 和原生约定好,通过原生劫持 JS 发出的请求进行原生调用
    • webView 添加要调用的原生方法接口,直接调用
    • 利用第三方库实现,如 Andriod 第三方库 JSBridge。安全便捷
  2. 原生调用 JS 方法
    • 直接使用 webView.evaluateJavacript()实现
    • 利用三方库,如 JSBridge 来实现

缓存用的是什么

  • AsyncStorage 它是一个简单的、异步的、持久化的键值对存储系统,它对于 App 来说是全局的。可以用来替代 LocalStorage
  • 官网推荐在此基础上封装一层,不要直接使用
  • 在 IOS 上,AsyncStorage 在原生端的实现是把较小值存放在序列化的字典中,而把较大值写入单独的文件
  • 在 Android 上,AsyncStorage 会尝试使用 RocksDB,或退而选择 SQLite

单页应用和多页应用

单页应用 (SPA) 只在初始化时加载主要资源,通过路由控制页面内容切换,提供流畅用户体验。多页应用 (MPA) 每次请求新页面都重新加载完整资源,适合内容丰富、SEO 要求高的应用。

区别

  1. 页面加载方式
    • 单页应用:只在应用初始化时加载页面的主要资源,之后页面内容的切换通过异步加载实现,不会重新加载整个页面。
    • 多页应用:每次用户请求新页面时,服务器都会返回一个完整的页面,包括新的 HTML、CSS 和 JavaScript。
  2. 页面切换
    • 单页应用:页面切换时通常是通过路由进行控制,以及通过前端框架 (如 Vue Router) 来管理视图的变化,不会导致整个页面的重新加载。
    • 多页应用:页面切换会触发整个页面的重新加载,因为每个页面都是独立的。
  3. 用户体验
    • 单页应用:提供更流畅的用户体验,因为页面切换时无需等待整个页面的重新加载。
    • 多页应用:可能存在页面切换时的延迟,因为需要重新加载整个页面。
  4. 开发复杂度
    • 单页应用:相对于多页应用,单页应用通常需要更多的前端技术栈和复杂的路由管理。
    • 多页应用:每个页面都是独立的,开发相对简单,但随着页面增多,维护成本可能会增加。
  5. SEO
    • 单页应用:需要特殊处理才能更好地支持搜索引擎优化 (SEO) ,因为页面内容是动态加载的。
    • 多页应用:每个页面都是独立的,更容易被搜索引擎索引。

单页应用的理解

SPA (Single Page Application) 单页面应用指的是在加载页面时,只需加载一次 HTML、CSS 和 JavaScript。在用户与应用程序交互时,页面不会重新加载,而是通过 AJAX 技术动态地更新页面内容。通常,SPA 通过路由管理来实现页面内容的切换,从而提供更流畅的用户体验。

优点

  1. 快速响应: 由于页面只在初始化时加载一次,之后的页面切换都是通过异步加载数据和更新 DOM,因此能够提供更快的响应速度。
  2. 良好的用户体验: SPA 能够提供类似原生应用的用户体验,避免了页面刷新带来的延迟,同时也避免了页面闪烁。
  3. 前后端分离: 前端负责 UI 和交互逻辑,后端则负责数据处理和接口的提供,使得开发更加清晰和高效。
  4. 减少服务器负担: 由于减少了页面的加载次数,可以减轻服务器的负担,提高服务器性能。
  5. 适合 Web 应用: 对于需要频繁交互和动态更新的 Web 应用来说,SPA 是一个非常合适的选择。

缺点

  1. 首次加载时间较长: 首次加载可能会包含大量的 JavaScript、CSS 和模板文件,导致首次加载时间较长。
    • 代码分割 (Code Splitting)
    • 懒加载 (Lazy Loading)
    • 资源压缩和优化
    • CDN 加速
    • 预加载 (Preloading)
    • 服务端渲染 (Server-Side Rendering)
    • 性能监控与优化
  2. SEO 难度: 对于搜索引擎来说,由于内容都是通过 JavaScript 动态加载的,爬虫不易获取到完整的页面内容,影响 SEO 优化。
    • 服务端渲染 (SSR) 或预渲染
    • 动态路由和静态路由混合使用:对于一些静态内容,可以采用静态路由,这样搜索引擎爬虫更容易抓取这部分内容。对于需要动态加载的内容,可以采用动态路由,以保持良好的用户体验。
    • 合理的 URL 结构
    • 合理的元数据:meta 标签,包括 title、description 和关键字等
    • Sitemap 和 Robots.txt:创建并提交 Sitemap,同时配置 Robots.txt 文件,以指导搜索引擎爬虫更有效地抓取和索引网站内容。
    • 使用动态渲染服务
    • 监控和测试
  3. 内存占用: 长时间运行的单页面应用可能会导致内存占用过多,特别是在移动设备上。
    • 组件销毁与内存管理
    • 懒加载与按需加载
    • 虚拟列表与无限滚动
    • 性能监控与优化
    • 资源释放与缓存管理
    • 定期升级与优化框架
  4. 安全性: 因为 SPA 通常需要从服务端一次性加载所有的代码,可能存在一些安全隐患。
    • 客户端数据可被窃取
    • 跨站脚本攻击 (XSS)
    • 数据篡改
    • 安全策略:在 SPA 中,需要特别注意跨域资源共享 (CORS) 和安全头部设置,以防止恶意站点利用客户端漏洞攻击服务端或其他站点。

针对以上问题,有以下改进措施

  1. 合理的权限控制: 对于敏感操作和数据,需要进行严格的权限控制,确保只有经过授权的用户才能访问和操作。
  2. 数据加密
  3. 输入验证与过滤
  4. 安全头部设置:使用适当的 HTTP 头部设置,如 Content Security Policy (CSP)、X-Content-Type-Options、X-XSS-Protection 等,以增强安全性。
  5. 定期安全审计