Heim > Web-Frontend > View.js > Ein Artikel, der ausführlich erklärt, wie Vue3 globale Ausnahmen behandelt

Ein Artikel, der ausführlich erklärt, wie Vue3 globale Ausnahmen behandelt

青灯夜游
Freigeben: 2022-03-07 19:54:22
nach vorne
3719 Leute haben es durchsucht

Wie implementiert Vue3 die globale Ausnahmebehandlung? Der folgende Artikel stellt Ihnen die Methode der globalen Ausnahmebehandlung in Vue3 vor. Ich hoffe, er wird Ihnen hilfreich sein!

Ein Artikel, der ausführlich erklärt, wie Vue3 globale Ausnahmen behandelt

Bei der Entwicklung von Komponentenbibliotheken oder Plug-Ins ist häufig eine globale Ausnahmebehandlung erforderlich, um Folgendes zu erreichen:

    Prompte Fehlermeldungen für Entwickler;
  • Projektverschlechterungsverarbeitung usw.
  • Wie implementiert man also die obige Funktion? In diesem Artikel wird zunächst kurz eine Ausnahmebehandlungsmethode implementiert, diese dann anhand der Implementierung im Vue3-Quellcode ausführlich vorgestellt und schließlich einige Kernelemente für die Implementierung der Ausnahmebehandlung zusammengefasst. [Verwandte Empfehlungen:
  • vuejs-Video-Tutorial
]

Die Vue3-Version dieses Artikels ist 3.0.11

1. Häufige Front-End-Ausnahmen

Für das Front-End gibt es viele häufige Ausnahmen, wie zum Beispiel:

JS -Syntaxausnahme; onerror

error: Error object

(object)

Wenn dies Die Funktion gibt „true“ zurück und verhindert dann die Ausführung des Standard-Ereignishandlers. window.onerror文档可知,当 JS 运行时发生错误(包括语法错误),触发 window.onerror()

try {
  // do something
} catch (error) {
  console.error(error);
}
Nach dem Login kopieren

函数参数:

  • message:错误信息(字符串)。可用于HTML onerror=""处理程序中的 event
  • source:发生错误的脚本URL(字符串)
  • lineno:发生错误的行号(数字)
  • colno:发生错误的列号(数字)
  • error:Error对象(对象)

若该函数返回true,则阻止执行默认事件处理函数。

2. try...catch 异常处理

另外,我们也经常会使用 try...catch 语句处理异常:

const errorHandling = (fn, args) => {
  let result;
  try{
    result = args ? fn(...args) : fn();
  } catch (error){
    console.error(error)
  }
  return result;
}
Nach dem Login kopieren

更多处理方式,可以阅读前面推荐的文章。

3. 思考

大家可以思考下,自己在业务开发过程中,是否也是经常要处理这些错误情况? 那么像 Vue3 这样复杂的库,是否也是到处通过 try...catch来处理异常呢? 接下来一起看看。

二、实现简单的全局异常处理

在开发插件或库时,我们可以通过 try...catch封装一个全局异常处理方法,将需要执行的方法作为参数传入,调用方只要关心调用结果,而无需知道该全局异常处理方法内部逻辑。 大致使用方法如下:

const f1 = () => {
    console.log('[f1 running]')
    throw new Error('[f1 error!]')
}

errorHandling(f1);
/*
 输出:
 [f1 running]
Error: [f1 error!]
    at f1 (/Users/wangpingan/leo/www/node/www/a.js:14:11)
    at errorHandling (/Users/wangpingan/leo/www/node/www/a.js:4:39)
    at Object.<anonymous> (/Users/wangpingan/leo/www/node/www/a.js:17:1)
    at Module._compile (node:internal/modules/cjs/loader:1095:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1147:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47
*/
Nach dem Login kopieren

测试一下:

const f1 = () => {
    console.log(&#39;[f1]&#39;)
    f2();
}

const f2 = () => {
    console.log(&#39;[f2]&#39;)
    f3();
}

const f3 = () => {
    console.log(&#39;[f3]&#39;)
    throw new Error(&#39;[f3 error!]&#39;)
}

errorHandling(f1)
/*
  输出:
  [f1 running]
  [f2 running]
  [f3 running]
  Error: [f3 error!]
    at f3 (/Users/wangpingan/leo/www/node/www/a.js:24:11)
    at f2 (/Users/wangpingan/leo/www/node/www/a.js:19:5)
    at f1 (/Users/wangpingan/leo/www/node/www/a.js:14:5)
    at errorHandling (/Users/wangpingan/leo/www/node/www/a.js:4:39)
    at Object.<anonymous> (/Users/wangpingan/leo/www/node/www/a.js:27:1)
    at Module._compile (node:internal/modules/cjs/loader:1095:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1147:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
*/
Nach dem Login kopieren

可以看到,当需要为方法做异常处理时,只要将该方法作为参数传入即可。 但是上面示例跟实际业务开发的逻辑差得有点多,实际业务中,我们经常会遇到方法的嵌套调用,那么我们试一下:

callWithAsyncErrorHandling(
  handler,
  instance,
  ErrorCodes.COMPONENT_EVENT_HANDLER,
  args
)
Nach dem Login kopieren

这样也是没问题的。那么接下来就是在 errorHandling方法的 catch分支实现对应异常处理即可。 接下来看看 Vue3 源码中是如何处理的?

三、Vue3 如何实现异常处理

理解完上面示例,接下来看看在 Vue3 源码中是如何实现异常处理的,其实现起来也是很简单。

1. 实现异常处理方法

errorHandling.ts 文件中定义了 callWithErrorHandlingcallWithAsyncErrorHandling两个处理全局异常的方法。 顾名思义,这两个方法分别处理:

  • callWithErrorHandling:处理同步方法的异常;
  • callWithAsyncErrorHandling:处理异步方法的异常。

使用方式如下:

// packages/runtime-core/src/errorHandling.ts

// 处理同步方法的异常
export function callWithErrorHandling(
  fn: Function,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  args?: unknown[]
) {
  let res
  try {
    res = args ? fn(...args) : fn(); // 调用原方法
  } catch (err) {
    handleError(err, instance, type)
  }
  return res
}

// 处理异步方法的异常
export function callWithAsyncErrorHandling(
  fn: Function | Function[],
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  args?: unknown[]
): any[] {
  // 省略其他代码
  const res = callWithErrorHandling(fn, instance, type, args)
  if (res && isPromise(res)) {
    res.catch(err => {
      handleError(err, instance, type)
    })
  }
  // 省略其他代码
}
Nach dem Login kopieren

代码实现大致如下:

// packages/runtime-core/src/errorHandling.ts

// 异常处理方法
export function handleError(
  err: unknown,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  throwInDev = true
) {
  // 省略其他代码
  logError(err, type, contextVNode, throwInDev)
}

function logError(
  err: unknown,
  type: ErrorTypes,
  contextVNode: VNode | null,
  throwInDev = true
) {
  // 省略其他代码
  console.error(err)
}
Nach dem Login kopieren

callWithErrorHandling方法处理的逻辑比较简单,通过简单的 try...catch 做一层封装。 而 callWithAsyncErrorHandling 方法就比较巧妙,通过将需要执行的方法传入 callWithErrorHandling方法处理,并将其结果通过 .catch方法进行处理。

2. 处理异常

在上面代码中,遇到报错的情况,都会通过 handleError()处理异常。其实现大致如下:

// src/main.js

app.config.errorHandler = (err, vm, info) => {
  // 处理错误
  // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
}
Nach dem Login kopieren
Nach dem Login kopieren

保留核心处理逻辑之后,可以看到这边处理也是相当简单,直接通过 console.error(err)

2. try...catch-Ausnahmebehandlung

🎜Darüber hinaus verwenden wir häufig try...catch Statement 🎜Ausnahmebehandlung: 🎜
// packages/runtime-core/src/errorHandling.ts

export function handleError(
  err: unknown,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  throwInDev = true
) {
  const contextVNode = instance ? instance.vnode : null
  if (instance) {
    // 省略其他代码
    // 读取 errorHandler 配置项
    const appErrorHandler = instance.appContext.config.errorHandler
    if (appErrorHandler) {
      callWithErrorHandling(
        appErrorHandler,
        null,
        ErrorCodes.APP_ERROR_HANDLER,
        [err, exposedInstance, errorInfo]
      )
      return
    }
  }
  logError(err, type, contextVNode, throwInDev)
}
Nach dem Login kopieren
Nach dem Login kopieren
🎜Mehr Behandlungsmethode, Sie können den zuvor empfohlenen Artikel lesen. 🎜

3. Nachdenken🎜🎜Sie können darüber nachdenken, müssen Sie sich während des Geschäftsentwicklungsprozesses häufig mit diesen Fehlersituationen auseinandersetzen? Verwendet eine komplexe Bibliothek wie Vue3 also try...catch, um überall Ausnahmen zu behandeln? Werfen wir als nächstes einen Blick darauf. 🎜

2. Einfache globale Ausnahmebehandlung implementieren🎜🎜Bei der Entwicklung von Plug-Ins oder Bibliotheken können wir eine globale Ausnahmebehandlung durch try...catch kapseln Methode: Übergeben Sie die auszuführende Methode als Parameter. Der Aufrufer kümmert sich nur um das Aufrufergebnis und muss die interne Logik der globalen Ausnahmebehandlungsmethode nicht kennen. Die allgemeine Verwendung ist wie folgt: 🎜
(err: Error, instance: Component, info: string) => ?boolean
Nach dem Login kopieren
Nach dem Login kopieren
🎜Testen Sie es: 🎜
<template>
  <Message></Message>
</template>
<script setup>
// App.vue  
import { onErrorCaptured } from &#39;vue&#39;;
  
import Message from &#39;./components/Message.vue&#39;
  
onErrorCaptured(function(err, instance, info){
  console.log(&#39;[errorCaptured]&#39;, err, instance, info)
})
</script>
Nach dem Login kopieren
Nach dem Login kopieren
🎜Sie können sehen, dass Sie die Methode nur als Parameter übergeben müssen, wenn Sie Ausnahmen für eine Methode behandeln müssen. Das obige Beispiel unterscheidet sich jedoch ein wenig von der Logik der tatsächlichen Geschäftsentwicklung. Im tatsächlichen Geschäft stoßen wir häufig auf verschachtelte Methodenaufrufe. Versuchen wir es also: 🎜
<template>
  <button @click="sendMessage">发送消息</button>
</template>

<script setup>
// Message.vue
const sendMessage = () => {
  throw new Error(&#39;[test onErrorCaptured]&#39;)
}
</script>
Nach dem Login kopieren
Nach dem Login kopieren
🎜 Das ist kein Problem. Anschließend besteht der nächste Schritt darin, die entsprechende Ausnahmebehandlung im Zweig catch der Methode errorHandling zu implementieren. Schauen wir uns als nächstes an, wie es im Vue3-Quellcode gehandhabt wird. 🎜

3. ​​​​So implementieren Sie die Ausnahmebehandlung in Vue3🎜🎜Nachdem wir das obige Beispiel verstanden haben, schauen wir uns an, wie die Ausnahmebehandlung im Vue3-Quellcode implementiert wird auch sehr einfach umzusetzen. 🎜

1. Methoden zur Ausnahmebehandlung implementieren 🎜🎜 Definieren Sie callWithErrorHandling und callWithAsyncErrorHandling< in der Datei <code>errorHandling.ts / code>Zwei Methoden zur Behandlung globaler Ausnahmen. Wie der Name schon sagt, behandeln diese beiden Methoden jeweils: 🎜🎜🎜callWithErrorHandling: behandelt Ausnahmen in synchronen Methoden; 🎜🎜callWithAsyncErrorHandling: behandelt Ausnahmen in asynchronen Methoden. 🎜🎜🎜Die Verwendungsmethode ist wie folgt: 🎜
[errorCaptured] Error: [test onErrorCaptured]
    at Proxy.sendMessage (Message.vue:36:15)
    at _createElementVNode.onClick._cache.<computed>._cache.<computed> (Message.vue:3:39)
    at callWithErrorHandling (runtime-core.esm-bundler.js:6706:22)
    at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:6715:21)
    at HTMLButtonElement.invoker (runtime-dom.esm-bundler.js:350:13) Proxy {sendMessage: ƒ, …} native event handler
Nach dem Login kopieren
Nach dem Login kopieren
🎜Die Code-Implementierung ist ungefähr wie folgt: 🎜
// packages/runtime-core/src/errorHandling.ts

export function handleError(
  err: unknown,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  throwInDev = true
) {
  const contextVNode = instance ? instance.vnode : null
  if (instance) {
    let cur = instance.parent
    // the exposed instance is the render proxy to keep it consistent with 2.x
    const exposedInstance = instance.proxy
    // in production the hook receives only the error code
    const errorInfo = __DEV__ ? ErrorTypeStrings[type] : type
    while (cur) {
      const errorCapturedHooks = cur.ec // ①取出组件配置的 errorCaptured 生命周期方法
      if (errorCapturedHooks) {
        // ②循环执行 errorCaptured 中的每个 Hook
        for (let i = 0; i < errorCapturedHooks.length; i++) {
          if (
            errorCapturedHooks[i](err, exposedInstance, errorInfo) === false
          ) {
            return
          }
        }
      }
      cur = cur.parent
    }
    // 省略其他代码
  }
  logError(err, type, contextVNode, throwInDev)
}
Nach dem Login kopieren
Nach dem Login kopieren
🎜Die Logik der callWithErrorHandling-Methode ist durch einen einfachen try relativ einfach. .catch-Schichtenkapselung. Die Methode callWithAsyncErrorHandling ist cleverer. Sie übergibt die auszuführende Methode zur Verarbeitung an die Methode callWithErrorHandling und das Ergebnis wird über .catch< verarbeitet /code> Methode. 🎜<h3 data-id="heading-7">2. Ausnahmen behandeln🎜🎜Wenn im obigen Code ein Fehler gemeldet wird, wird die Ausnahme über <code>handleError() behandelt. Die Implementierung sieht ungefähr wie folgt aus: 🎜
// packages/runtime-core/src/errorHandling.ts

export const enum ErrorCodes {
  SETUP_FUNCTION,
  RENDER_FUNCTION,
  WATCH_GETTER,
  WATCH_CALLBACK,
  // ... 省略其他
}

export const ErrorTypeStrings: Record<number | string, string> = {
  // 省略其他
  [LifecycleHooks.RENDER_TRACKED]: &#39;renderTracked hook&#39;,
  [LifecycleHooks.RENDER_TRIGGERED]: &#39;renderTriggered hook&#39;,
  [ErrorCodes.SETUP_FUNCTION]: &#39;setup function&#39;,
  [ErrorCodes.RENDER_FUNCTION]: &#39;render function&#39;,
  // 省略其他
  [ErrorCodes.SCHEDULER]:
    &#39;scheduler flush. This is likely a Vue internals bug. &#39; +
    &#39;Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-next&#39;
}
Nach dem Login kopieren
Nach dem Login kopieren
🎜Nachdem Sie die Kernverarbeitungslogik beibehalten haben, können Sie sehen, dass die Verarbeitung auch hier recht einfach ist und der Fehlerinhalt direkt über console.error(err) ausgegeben wird . 🎜

3. 配置 errorHandler 自定义异常处理函数

在使用 Vue3 时,也支持指定自定义异常处理函数,来处理组件渲染函数侦听器执行期间抛出的未捕获错误。这个处理函数被调用时,可获取错误信息和相应的应用实例。 文档参考:《errorHandler》 使用方法如下,在项目 main.js文件中配置:

// src/main.js

app.config.errorHandler = (err, vm, info) => {
  // 处理错误
  // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
}
Nach dem Login kopieren
Nach dem Login kopieren

那么 errorHandler()是何时执行的呢?我们继续看看源码中 handleError() 的内容,可以发现:

// packages/runtime-core/src/errorHandling.ts

export function handleError(
  err: unknown,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  throwInDev = true
) {
  const contextVNode = instance ? instance.vnode : null
  if (instance) {
    // 省略其他代码
    // 读取 errorHandler 配置项
    const appErrorHandler = instance.appContext.config.errorHandler
    if (appErrorHandler) {
      callWithErrorHandling(
        appErrorHandler,
        null,
        ErrorCodes.APP_ERROR_HANDLER,
        [err, exposedInstance, errorInfo]
      )
      return
    }
  }
  logError(err, type, contextVNode, throwInDev)
}
Nach dem Login kopieren
Nach dem Login kopieren

通过 instance.appContext.config.errorHandler取到全局配置的自定义错误处理函数,存在时则执行,当然,这边也是通过前面定义的 callWithErrorHandling来调用。

4. 调用 errorCaptured 生命周期钩子

在使用 Vue3 的时候,也可以通过 errorCaptured生命周期钩子来捕获来自后代组件的错误。 文档参考:《errorCaptured》 入参如下:

(err: Error, instance: Component, info: string) => ?boolean
Nach dem Login kopieren
Nach dem Login kopieren

此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。 此钩子可以返回 false阻止该错误继续向上传播。有兴趣的同学可以通过文档,查看具体的错误传播规则。 使用方法如下,父组件监听 onErrorCaptured生命周期(示例代码使用 Vue3 setup 语法):

<template>
  <Message></Message>
</template>
<script setup>
// App.vue  
import { onErrorCaptured } from &#39;vue&#39;;
  
import Message from &#39;./components/Message.vue&#39;
  
onErrorCaptured(function(err, instance, info){
  console.log(&#39;[errorCaptured]&#39;, err, instance, info)
})
</script>
Nach dem Login kopieren
Nach dem Login kopieren

子组件如下:

<template>
  <button @click="sendMessage">发送消息</button>
</template>

<script setup>
// Message.vue
const sendMessage = () => {
  throw new Error(&#39;[test onErrorCaptured]&#39;)
}
</script>
Nach dem Login kopieren
Nach dem Login kopieren

当点击「发送消息」按钮,控制台便输出错误:

[errorCaptured] Error: [test onErrorCaptured]
    at Proxy.sendMessage (Message.vue:36:15)
    at _createElementVNode.onClick._cache.<computed>._cache.<computed> (Message.vue:3:39)
    at callWithErrorHandling (runtime-core.esm-bundler.js:6706:22)
    at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:6715:21)
    at HTMLButtonElement.invoker (runtime-dom.esm-bundler.js:350:13) Proxy {sendMessage: ƒ, …} native event handler
Nach dem Login kopieren
Nach dem Login kopieren

可以看到 onErrorCaptured生命周期钩子正常执行,并输出子组件 Message.vue内的异常。

那么这个又是如何实现呢?还是看 errorHandling.ts 中的 handleError() 方法:

// packages/runtime-core/src/errorHandling.ts

export function handleError(
  err: unknown,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  throwInDev = true
) {
  const contextVNode = instance ? instance.vnode : null
  if (instance) {
    let cur = instance.parent
    // the exposed instance is the render proxy to keep it consistent with 2.x
    const exposedInstance = instance.proxy
    // in production the hook receives only the error code
    const errorInfo = __DEV__ ? ErrorTypeStrings[type] : type
    while (cur) {
      const errorCapturedHooks = cur.ec // ①取出组件配置的 errorCaptured 生命周期方法
      if (errorCapturedHooks) {
        // ②循环执行 errorCaptured 中的每个 Hook
        for (let i = 0; i < errorCapturedHooks.length; i++) {
          if (
            errorCapturedHooks[i](err, exposedInstance, errorInfo) === false
          ) {
            return
          }
        }
      }
      cur = cur.parent
    }
    // 省略其他代码
  }
  logError(err, type, contextVNode, throwInDev)
}
Nach dem Login kopieren
Nach dem Login kopieren

这边会先获取 instance.parent作为当前处理的组件实例进行递归,每次将取出组件配置的 errorCaptured 生命周期方法的数组并循环调用其每一个钩子,然后再取出当前组件的父组件作为参数,最后继续递归调用下去。

5. 实现错误码和错误消息

Vue3 还为异常定义了错误码和错误信息,在不同的错误情况有不同的错误码和错误信息,让我们能很方便定位到发生异常的地方。 错误码和错误信息如下:

// packages/runtime-core/src/errorHandling.ts

export const enum ErrorCodes {
  SETUP_FUNCTION,
  RENDER_FUNCTION,
  WATCH_GETTER,
  WATCH_CALLBACK,
  // ... 省略其他
}

export const ErrorTypeStrings: Record<number | string, string> = {
  // 省略其他
  [LifecycleHooks.RENDER_TRACKED]: &#39;renderTracked hook&#39;,
  [LifecycleHooks.RENDER_TRIGGERED]: &#39;renderTriggered hook&#39;,
  [ErrorCodes.SETUP_FUNCTION]: &#39;setup function&#39;,
  [ErrorCodes.RENDER_FUNCTION]: &#39;render function&#39;,
  // 省略其他
  [ErrorCodes.SCHEDULER]:
    &#39;scheduler flush. This is likely a Vue internals bug. &#39; +
    &#39;Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-next&#39;
}
Nach dem Login kopieren
Nach dem Login kopieren

当不同错误情况,根据错误码 ErrorCodes来获取 ErrorTypeStrings错误信息进行提示:

// packages/runtime-core/src/errorHandling.ts

function logError(
  err: unknown,
  type: ErrorTypes,
  contextVNode: VNode | null,
  throwInDev = true
) {
  if (__DEV__) {
    const info = ErrorTypeStrings[type]
    warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
    // 省略其他
  } else {
    console.error(err)
  }
}
Nach dem Login kopieren

6. 实现 Tree Shaking

关于 Vue3 实现 Tree Shaking 的介绍,可以看我之前写的高效实现框架和 JS 库瘦身。 其中,logError 方法中就使用到了:

// packages/runtime-core/src/errorHandling.ts

function logError(
  err: unknown,
  type: ErrorTypes,
  contextVNode: VNode | null,
  throwInDev = true
) {
  if (__DEV__) {
    // 省略其他
  } else {
    console.error(err)
  }
}
Nach dem Login kopieren

当编译成 production 环境后,__DEV__分支的代码不会被打包进去,从而优化包的体积。

四、总结

到上面一部分,我们就差不多搞清楚 Vue3 中全局异常处理的核心逻辑了。我们在开发自己的错误处理方法时,也可以考虑这几个核心点:

  • 支持同步和异步的异常处理;

  • 设置业务错误码、业务错误信息;

  • 支持自定义错误处理方法;

  • 支持开发环境错误提示;

  • 支持 Tree Shaking。

原文地址:https://juejin.cn/post/7071982812668100616

(学习视频分享:vuejs教程web前端

Das obige ist der detaillierte Inhalt vonEin Artikel, der ausführlich erklärt, wie Vue3 globale Ausnahmen behandelt. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:juejin.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage