-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5ea1cfd
commit d8e9791
Showing
2 changed files
with
324 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,324 @@ | ||
--- | ||
highlight: darcula | ||
theme: smartblue | ||
--- | ||
|
||
# Taro 4.0 已发布 - 6. 为啥 Taro.xxx 能访问 wx.xxx ? | ||
|
||
## 1. 前言 | ||
|
||
大家好,我是[若川](https://juejin.cn/user/1415826704971918),欢迎关注我的[公众号:若川视野](https://mp.weixin.qq.com/s/MacNfeTPODNMLLFdzrULow)。我倾力持续组织了 3 年多[每周大家一起学习 200 行左右的源码共读活动](https://juejin.cn/post/7079706017579139102),感兴趣的可以[点此扫码加我微信 `ruochuan02` 参与](https://juejin.cn/pin/7217386885793595453)。另外,想学源码,极力推荐关注我写的专栏[《学习源码整体架构系列》](https://juejin.cn/column/6960551178908205093),目前是掘金关注人数(6k+人)第一的专栏,写有几十篇源码文章。 | ||
|
||
截至目前(`2024-08-18`),[`taro 4.0` 正式版已经发布](https://github.com/NervJS/taro/releases/tag/v4.0.3),目前最新是 `4.0.4`,官方`4.0`正式版本的介绍文章暂未发布。官方之前发过[Taro 4.0 Beta 发布:支持开发鸿蒙应用、小程序编译模式、Vite 编译等](https://juejin.cn/post/7330792655125463067)。 | ||
|
||
计划写一个 `taro` 源码揭秘系列,欢迎持续关注。 | ||
|
||
- [x] [Taro 源码揭秘 - 1. 揭开整个架构的入口 CLI => taro init 初始化项目的秘密](https://juejin.cn/post/7378363694939783178) | ||
- [x] [Taro 源码揭秘 - 2. 揭开整个架构的插件系统的秘密](https://juejin.cn/spost/7380195796208205824) | ||
- [x] [Taro 源码揭秘 - 3. 每次创建新的 taro 项目(taro init)的背后原理是什么](https://juejin.cn/post/7390335741586931738) | ||
- [x] [Taro 4.0 已正式发布 - 4. 每次 npm run dev:weapp 开发小程序,build 编译打包是如何实现的?](https://juejin.cn/post/7403193330271682612) | ||
- [x] [Taro 4.0 已发布 - 5.高手都在用的发布订阅机制 Events 在 Taro 中是如何实现的?](https://juejin.cn/post/7403915119448915977) | ||
- [ ] 等等 | ||
|
||
前面 4 篇文章都是讲述编译相关的,CLI、插件机制、初始化项目、编译构建流程。第 5 篇我们来讲些相对简单的,Taro 是如何实现发布订阅机制 Events 的。 | ||
|
||
学完本文,你将学到: | ||
|
||
```bash | ||
等等 | ||
``` | ||
|
||
## 2. tarojs/taro | ||
|
||
```ts | ||
// packages/taro/index.js | ||
const { hooks } = require('@tarojs/runtime') | ||
const taro = require('@tarojs/api').default | ||
|
||
if (hooks.isExist('initNativeApi')) { | ||
hooks.call('initNativeApi', taro) | ||
} | ||
|
||
module.exports = taro | ||
module.exports.default = module.exports | ||
``` | ||
|
||
## src/program.ts | ||
|
||
|
||
```ts | ||
// packages/taro-platform-weapp/src/program.ts | ||
import { TaroPlatformBase } from '@tarojs/service' | ||
|
||
// 省略若干代码 | ||
|
||
const PACKAGE_NAME = '@tarojs/plugin-platform-weapp' | ||
|
||
export default class Weapp extends TaroPlatformBase { | ||
template: Template | ||
platform = 'weapp' | ||
globalObject = 'wx' | ||
projectConfigJson: string = this.config.projectConfigName || 'project.config.json' | ||
runtimePath = `${PACKAGE_NAME}/dist/runtime` | ||
// 省略若干代码 | ||
} | ||
``` | ||
|
||
## src/runtime.ts | ||
|
||
```ts | ||
// packages/taro-platform-weapp/src/runtime.ts | ||
import { mergeInternalComponents, mergeReconciler } from '@tarojs/shared' | ||
|
||
import { components, hostConfig } from './runtime-utils' | ||
|
||
mergeReconciler(hostConfig) | ||
mergeInternalComponents(components) | ||
``` | ||
|
||
## mergeReconciler | ||
|
||
```ts | ||
// packages/shared/src/utils.ts | ||
import { hooks } from './runtime-hooks' | ||
export function mergeReconciler (hostConfig, hooksForTest?) { | ||
const obj = hooksForTest || hooks | ||
const keys = Object.keys(hostConfig) | ||
keys.forEach(key => { | ||
obj.tap(key, hostConfig[key]) | ||
}) | ||
} | ||
|
||
``` | ||
|
||
```ts | ||
// packages/shared/src/utils.ts | ||
``` | ||
|
||
## hostConfig | ||
|
||
```ts | ||
// packages/taro-platform-weapp/src/runtime-utils.ts | ||
import { Shortcuts, toCamelCase } from '@tarojs/shared' | ||
|
||
import { initNativeApi } from './apis' | ||
|
||
declare const getCurrentPages: any | ||
|
||
export { initNativeApi } | ||
export * from './apis-list' | ||
export * from './components' | ||
export const hostConfig = { | ||
initNativeApi, | ||
getMiniLifecycle (config) { | ||
const methods = config.page[5] | ||
if (methods.indexOf('onSaveExitState') === -1) { | ||
methods.push('onSaveExitState') | ||
} | ||
return config | ||
}, | ||
transferHydrateData (data, element, componentsAlias) { | ||
if (element.isTransferElement) { | ||
const pages = getCurrentPages() | ||
const page = pages[pages.length - 1] | ||
data[Shortcuts.NodeName] = element.dataName | ||
page.setData({ | ||
[toCamelCase(data.nn)]: data | ||
}) | ||
return { | ||
sid: element.sid, | ||
[Shortcuts.Text]: '', | ||
[Shortcuts.NodeName]: componentsAlias['#text']?._num || '8' | ||
} | ||
} | ||
}, | ||
} | ||
|
||
``` | ||
|
||
## initNativeApi | ||
|
||
```ts | ||
// packages/taro-platform-weapp/src/apis.ts | ||
import { processApis } from '@tarojs/shared' | ||
|
||
import { needPromiseApis } from './apis-list' | ||
|
||
declare const wx: any | ||
|
||
export function initNativeApi (taro) { | ||
processApis(taro, wx, { | ||
needPromiseApis, | ||
modifyApis (apis) { | ||
// fix https://github.com/NervJS/taro/issues/9899 | ||
apis.delete('lanDebug') | ||
}, | ||
transformMeta (api: string, options: Record<string, any>) { | ||
if (api === 'showShareMenu') { | ||
options.menus = options.showShareItems?.map(item => item === 'wechatFriends' ? 'shareAppMessage' : item === 'wechatMoment' ? 'shareTimeline' : item) | ||
} | ||
|
||
return { | ||
key: api, | ||
options | ||
} | ||
} | ||
}) | ||
taro.cloud = wx.cloud | ||
taro.getTabBar = function (pageCtx) { | ||
if (typeof pageCtx?.getTabBar === 'function') { | ||
return pageCtx.getTabBar()?.$taroInstances | ||
} | ||
} | ||
taro.getRenderer = function () { | ||
return taro.getCurrentInstance()?.page?.renderer ?? 'webview' | ||
} | ||
} | ||
|
||
``` | ||
|
||
## processApis | ||
|
||
```ts | ||
// packages/shared/src/native-apis.ts | ||
function processApis (taro, global, config: IProcessApisIOptions = {}) { | ||
const patchNeedPromiseApis = config.needPromiseApis || [] | ||
const _needPromiseApis = new Set<string>([...patchNeedPromiseApis, ...needPromiseApis]) | ||
const preserved = [ | ||
'getEnv', | ||
'interceptors', | ||
'Current', | ||
'getCurrentInstance', | ||
'options', | ||
'nextTick', | ||
'eventCenter', | ||
'Events', | ||
'preload', | ||
'webpackJsonp' | ||
] | ||
|
||
const apis = new Set( | ||
!config.isOnlyPromisify | ||
? Object.keys(global).filter(api => preserved.indexOf(api) === -1) | ||
: patchNeedPromiseApis | ||
) | ||
|
||
if (config.modifyApis) { | ||
config.modifyApis(apis) | ||
} | ||
|
||
apis.forEach(key => { | ||
if (_needPromiseApis.has(key)) { | ||
const originKey = key | ||
taro[originKey] = (options: Record<string, any> | string = {}, ...args) => { | ||
let key = originKey | ||
|
||
// 第一个参数 options 为字符串,单独处理 | ||
if (typeof options === 'string') { | ||
if (args.length) { | ||
return global[key](options, ...args) | ||
} | ||
return global[key](options) | ||
} | ||
|
||
// 改变 key 或 option 字段,如需要把支付宝标准的字段对齐微信标准的字段 | ||
if (config.transformMeta) { | ||
const transformResult = config.transformMeta(key, options) | ||
key = transformResult.key | ||
; (options as Record<string, any>) = transformResult.options | ||
// 新 key 可能不存在 | ||
if (!global.hasOwnProperty(key)) { | ||
return nonsupport(key)() | ||
} | ||
} | ||
|
||
let task: any = null | ||
const obj: Record<string, any> = Object.assign({}, options) | ||
|
||
// 为页面跳转相关的 API 设置一个随机数作为路由参数。为了给 runtime 区分页面。 | ||
setUniqueKeyToRoute(key, options) | ||
|
||
// Promise 化 | ||
const p: any = new Promise((resolve, reject) => { | ||
obj.success = res => { | ||
config.modifyAsyncResult?.(key, res) | ||
options.success?.(res) | ||
if (key === 'connectSocket') { | ||
resolve( | ||
Promise.resolve().then(() => task ? Object.assign(task, res) : res) | ||
) | ||
} else { | ||
resolve(res) | ||
} | ||
} | ||
obj.fail = res => { | ||
options.fail?.(res) | ||
reject(res) | ||
} | ||
obj.complete = res => { | ||
options.complete?.(res) | ||
} | ||
if (args.length) { | ||
task = global[key](obj, ...args) | ||
} else { | ||
task = global[key](obj) | ||
} | ||
}) | ||
|
||
// 给 promise 对象挂载属性 | ||
if (['uploadFile', 'downloadFile'].includes(key)) { | ||
equipTaskMethodsIntoPromise(task, p) | ||
p.progress = cb => { | ||
task?.onProgressUpdate(cb) | ||
return p | ||
} | ||
p.abort = cb => { | ||
cb?.() | ||
task?.abort() | ||
return p | ||
} | ||
} | ||
return p | ||
} | ||
} else { | ||
let platformKey = key | ||
|
||
// 改变 key 或 option 字段,如需要把支付宝标准的字段对齐微信标准的字段 | ||
if (config.transformMeta) { | ||
platformKey = config.transformMeta(key, {}).key | ||
} | ||
|
||
// API 不存在 | ||
if (!global.hasOwnProperty(platformKey)) { | ||
taro[key] = nonsupport(key) | ||
return | ||
} | ||
if (isFunction(global[key])) { | ||
taro[key] = (...args) => { | ||
if (config.handleSyncApis) { | ||
return config.handleSyncApis(key, global, args) | ||
} else { | ||
return global[platformKey].apply(global, args) | ||
} | ||
} | ||
} else { | ||
taro[key] = global[platformKey] | ||
} | ||
} | ||
}) | ||
|
||
!config.isOnlyPromisify && equipCommonApis(taro, global, config) | ||
} | ||
|
||
``` | ||
|
||
## 总结 | ||
|
||
|
||
---- | ||
|
||
**如果看完有收获,欢迎点赞、评论、分享、收藏支持。你的支持和肯定,是我写作的动力。也欢迎提建议和交流讨论**。 | ||
|
||
作者:常以**若川**为名混迹于江湖。所知甚少,唯善学。[若川的博客](https://ruochuan12.github.io),[github blog](https://github.com/ruochuan12/blog),可以点个 `star` 鼓励下持续创作。 | ||
|
||
最后可以持续关注我[@若川](https://juejin.cn/user/1415826704971918),欢迎关注我的[公众号:若川视野](https://mp.weixin.qq.com/s/MacNfeTPODNMLLFdzrULow)。我倾力持续组织了 3 年多[每周大家一起学习 200 行左右的源码共读活动](https://juejin.cn/post/7079706017579139102),感兴趣的可以[点此扫码加我微信 `ruochuan02` 参与](https://juejin.cn/pin/7217386885793595453)。另外,想学源码,极力推荐关注我写的专栏[《学习源码整体架构系列》](https://juejin.cn/column/6960551178908205093),目前是掘金关注人数(6k+人)第一的专栏,写有几十篇源码文章。 |
This file was deleted.
Oops, something went wrong.