2022年06月20-07月01日 开发小计

294 阅读6分钟

由于本周(06/20-06/24)没有太多需要记录的问题, 所以与下周一同记录.

在 react-native-web 中使用 webview

已更新至开发小计

CSS 实现圆角渐变边框

在线预览和查看源码

image.png

实现原理是三层 div

  • 外层 div, 提供大小
    • 中间 div, 提供渐变背景, 并且通过 padding-rightpadding-bottom模拟右边和下面的边框, 通过 border-radius设置圆角
      • 内层 div, 设置背景覆盖中层 div 的背景, 并且与中间 设置同样的圆角尺寸.

在基于 Expo 的 RN 项目中读取本地文件的原始内容

首先, 如果你不使用 Expo, 或者使用 Expo 的 bare 模式, 那你可以直接使用 react-native-fs, 这是简单安全的方式.

解决方案

  1. 让 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. 获取文件内容
// 获取模块, 一个数字自增值, 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 文件扩展读取就不多讲了, 主要讲第二部分.

  1. 通过 require(or import)获取到模块 id, 仅获得模块 id 是不够的, 我们要获取模块的具体内容.
  2. 获取模块元信息, 下面代码仅为 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);
}

  1. 从模块元信息获取 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,
  };
}

好了, 现在在复盘下

  1. 根据 require 或者 import 获取模块id
  2. 根据 模块id 获取文件 hash
  3. 根据 文件hash 获取文件 uri

通过这三个步骤就可以实现在 expo 的托管模式中读取到文件原始信息.

小声 bb: 这里还有个坑就是模块元信息的 hash 可能和文件的 hash 不一样, 这里笔者没遇到, 有机会再深入解析吧.

不要相信广东的天气预报

不要相关广东的天气预报

不要相关广东的天气预报

不要相关广东的天气预报

不要相关广东的天气预报

不要相关广东的天气预报