由于本周(06/20-06/24)没有太多需要记录的问题, 所以与下周一同记录.
在 react-native-web 中使用 webview
已更新至开发小计
CSS 实现圆角渐变边框
实现原理是三层 div
- 外层 div, 提供大小
- 中间 div, 提供渐变背景, 并且通过
padding-right和padding-bottom模拟右边和下面的边框, 通过border-radius设置圆角- 内层 div, 设置背景覆盖中层 div 的背景, 并且与中间 设置同样的圆角尺寸.
- 中间 div, 提供渐变背景, 并且通过
在基于 Expo 的 RN 项目中读取本地文件的原始内容
首先, 如果你不使用 Expo, 或者使用 Expo 的 bare 模式, 那你可以直接使用 react-native-fs, 这是简单安全的方式.
解决方案
- 让 metro (rn 的 webpack) 支持你要读取的文件格式, 参考如下配置, 不然 metro 可能无法引入文件.
// 找到项目根目录下的 metro.config.js, 如果没有就创建一个
// 获取默认的 metro 配置
const { getDefaultConfig } = require('@expo/metro-config');
const defaultConfig = getDefaultConfig(projectRoot);
// 增加你要读取的文件格式
defaultConfig.resolver.assetExts.push('txt')
// 导出该文件
module.exports = defaultConfig;
- 获取文件内容
// 获取模块, 一个数字自增值, 1 或 2 或 3 或 4 或 5 这样.
const module = require("./file.txt")
// 将模块转换为 asset 对象
const file = Asset.fromModule(module);
// 例如 asset 对象的 downloadAsync 下载文件至本地(如果有缓存则会用缓存)
file.downloadAsync()
.then(async (res) => {
// downloadAsync 后文件必定有 uri 属性, 将 uri 下载后调用其 .text 方法获取原始文件内容
const response = await fetch(file.uri);
const text = await response.text();
console.log("text", text);
});
so easy, 这样就解决了, 如果你想了解其细节, 可以看下面的源码解析部分.
刨根问底
第一部分增加 metro 文件扩展读取就不多讲了, 主要讲第二部分.
- 通过
require(or import)获取到模块 id, 仅获得模块 id 是不够的, 我们要获取模块的具体内容. - 获取模块元信息, 下面代码仅为
fromModule方法实现的部分代码, 中文注释由笔者添加, 完整源码.
static fromModule(virtualAssetModule: number | string): Asset {
// 在 Android 和 iOS 上运行时, 代码不会走到这里, 我们忽略这里的逻辑
if (typeof virtualAssetModule === 'string') {
return Asset.fromURI(virtualAssetModule);
}
// 通过模块 id 获取其元信息(模块的一些描述信息)
// 这个方法源码位于: https://github.com/facebook/react-native/blob/8bd3edec88148d0ab1f225d2119435681fbbba33/packages/assets/registry.js#L21
const meta = getAssetByID(virtualAssetModule);
// meta 对象大概长这个样子.
// 如果你读到这里, 只需要记住 hash 这个属性即可, 它是一个文件的唯一标识, 下面的对象 hash 涉及到笔者项目信息, 这里直接随便写了.
const 我是meta对象 = {
"__packager_asset":true,
"httpServerLocation":"/assets",
"scales:[1],
"hash":"123123123123123123",
"name":"readme",
"type":"txt",
"fileHashes":["123123123123123123"]
}
// 如果你没有使用 expo 服务器进行热更新托管则会走到这里, 这里我们忽略掉.
// Outside of the managed env we need the moduleId to initialize the asset
// because resolveAssetSource depends on it
if (!IS_ENV_WITH_UPDATES_ENABLED) {
const { uri } = resolveAssetSource(virtualAssetModule);
const asset = new Asset({
name: meta.name,
type: meta.type,
hash: meta.hash,
uri,
width: meta.width,
height: meta.height,
});
// TODO: FileSystem should probably support 'downloading' from drawable
// resources But for now it doesn't (it only supports raw resources) and
// React Native's Image works fine with drawable resource names for
// images.
if (Platform.OS === 'android' && !uri.includes(':') && (meta.width || meta.height)) {
asset.localUri = asset.uri;
asset.downloaded = true;
}
Asset.byHash[meta.hash] = asset;
return asset;
}
// 源码解析见下
return Asset.fromMetadata(meta);
}
- 从模块元信息获取 Asset 对象, 下面代码仅为
fromMetadata方法实现的部分代码, 中文注释由笔者添加, 完整源码.
static fromMetadata(meta: AssetMetadata): Asset {
// The hash of the whole asset, not to be confused with the hash of a specific file returned
// from `selectAssetSource`
const metaHash = meta.hash;
// Asset 是一个全局的静态类, 它有个 byHash 属性, 这个属性用于缓存已经从 hash 转换为 asset 的对象, 参考 21 行, 解析模块元信息得到的 asset 对象会被添加到 byHash 属性里面
// 先尝试从缓存获取, 如果没有再去解析元信息
if (Asset.byHash[metaHash]) {
return Asset.byHash[metaHash];
}
// 关键代码来了, 请移步至 3.1, 看看 expo 是如何从模块元信息中获取原始文件信息.
const { uri, hash } = selectAssetSource(meta);
// 通过 selectAssetSource 方法获取到的信息, 将其组成一个 asset 对象.
const asset = new Asset({
name: meta.name,
type: meta.type,
hash,
uri,
width: meta.width,
height: meta.height,
});
// 存入缓存
Asset.byHash[metaHash] = asset;
return asset;
}
3.1 模块元信息中获取原始文件信息, 下面代码仅为 selectAssetSource方法实现的部分代码, 中文注释由笔者添加, 完整源码.
/**
* Selects the best file for the given asset (ex: choosing the best scale for images) and returns
* a { uri, hash } pair for the specific asset file.
*
* If the asset isn't an image with multiple scales, the first file is selected.
*/
export function selectAssetSource(meta: AssetMetadata): AssetSource {
// 记录模块 hash
const hash = meta.hash;
// 如果是开发环境(expo start 运行环境), 则使用其去拼接其本地服务的资源路径
// 结果大概为 http://192.168.31.35:10008/assets/readme.txt?platform=android&hash=8fe63e5c(隐去一部分)6645e04d?platform=android&dev=true&hot=false&strict=false&minify=false
// 通过这个 uri 就可以获取到文件的原始信息了
// For assets during development, we use the development server's URL origin
if (getManifest().developer) {
const baseUrl = new URL(getManifest().bundleUrl);
baseUrl.set('pathname', meta.httpServerLocation + suffix);
return { uri: baseUrl.href, hash };
}
// 如果是生成环境(即打包后运行), 则去某个 cdn 去获取, 相信大多数人看到这里都和笔者一样一脸懵逼, 首先不知道这个 cdn 是干嘛用的, 其次是担心文件安全.
// 笔者先来解答第一个问题, 如果我们使用 expo 的 managed 模式, 并且使用 expo 的热更新, 那么我们每次打包时资源文件和代码就会上传到 expo 服务器, 而 expo 就会将这些文件存在下面这个 cdn 上面, 最终通过这个路径访问 https://d1wp6m56sqw74a.cloudfront.net/~assets/8fe63e5c(隐去一部分)6645e04d 就可以获取到文件.
// 第二个问题, 其实只要了解了 hash 算法就知道, hash 算法作为一个碰撞率微乎及微的摘要算法, 在这里作为文件标识是没问题的, 也不用担心安全问题.
// 这里 expo 巧妙的运用了 hash 算法来获取资源的 uri.
// Production CDN URIs are based on each asset file hash
return {
uri: `https://d1wp6m56sqw74a.cloudfront.net/~assets/${encodeURIComponent(hash)}`,
hash,
};
}
好了, 现在在复盘下
- 根据 require 或者 import 获取模块id
- 根据 模块id 获取文件 hash
- 根据 文件hash 获取文件 uri
通过这三个步骤就可以实现在 expo 的托管模式中读取到文件原始信息.
小声 bb: 这里还有个坑就是模块元信息的 hash 可能和文件的 hash 不一样, 这里笔者没遇到, 有机会再深入解析吧.
不要相信广东的天气预报
不要相关广东的天气预报
不要相关广东的天气预报
不要相关广东的天气预报
不要相关广东的天气预报
不要相关广东的天气预报