Home > Web Front-end > Vue.js > body text

25 Vue tips you deserve to know

青灯夜游
Release: 2022-03-09 19:58:24
forward
2617 people have browsed it

This article summarizes and shares 25 things worth knowing about Vue Use tips to improve your development speed. I hope it will be helpful to everyone!

25 Vue tips you deserve to know

1. Limit the prop to a list of types

Using the validator option in the prop definition, you can limit the prop to a list of types. Group-specific values:

export default {
  name: 'Image',
  props: {
    src: {
      type: String,
    },
    style: {
      type: String,
      validator: s => ['square', 'rounded'].includes(s)
    }
  }
};
Copy after login

This validator function accepts a prop and returns true or false. It can also be used when you need more options than the boolean allows. Button types or alert types (info, success, danger, warning) are some of the more common uses. [Related recommendations: vuejs video tutorial]

2. Default content and extension points

Slots in Vue can have default content, and you can make components that are easier to use. :

<button class="button" @click="$emit(&#39;click&#39;)">
  <slot>
    <!-- 如果没有提供slot则使用 -->
    Click me
  </slot>
</button>
Copy after login

Basically you can take any part of the component, wrap it in a slot, and then you can override that part of the component with whatever you want. By default, it will still work as usual, but there are more options:

<template>
  <button class="button" @click="$emit(&#39;click&#39;)">
    <!-- 一开始在 slot 标签中添加什么都不做 -->
    <!-- 我们可以通过向 slot 提供内容来覆盖它 -->
    <slot>
      <div class="formatting">
        {{ text }}
      </div>
    </slot>
  </button>
</template>
Copy after login

Now you can use this component in many different ways. Simple default way or your own custom way:

<!-- 使用组件的默认功能 -->
<ButtonWithExtensionPoint text="Formatted text" />

<!-- 使用扩展点创建自定义行为 -->
<ButtonWithExtensionPoint>
  <div class="different-formatting">
    在这里做一些不同的事情
  </div>
</ButtonWithExtensionPoint>
Copy after login

3. Use quotes to observe nested values

You may not know this: just use quotes to easily view them directly Nested values:

watch {
  &#39;$route.query.id&#39;() {
    // ...
  }
}
Copy after login

This is useful for working with deeply nested objects

4. Know when to use v-if (and when to avoid it)

Sometimes Instead of using v-if, it would be more efficient to use v-show:

<ComplicatedChart v-show="chartEnabled" />
Copy after login

When v-if is turned on and off, it will Completely creates and destroys elements. v-showThe difference is that will create the element and leave it there, hiding it by setting its style to display: none.

If the component you need to switch is more expensive to render, then this will be more efficient. On the other hand, if you don't need that component right away, you can use v-if so that it skips rendering it and loads the page faster.

5. Short for single-scope slot (no template tags required!)

Scoped slots are more interesting, but in order to use them, you also have to use a lot of template tags .

However there is a shorthand that allows us to get rid of it, but only if we use a single scope slot.

You don’t need to write it like this:

<DataTable>
  <template #header="tableAttributes">
    <TableHeader v-bind="tableAttributes" />
  </template>
</DataTable>
Copy after login

We can write it like this:

<DataTable #header="tableAttributes">
  <TableHeader v-bind="tableAttributes" />
</DataTable>
Copy after login

This is simpler and more direct.

6. Conditionally render slots

Each Vue component has a special $slots object that contains all slots. Default slots have default keys, and named slots use their names as keys:

const $slots = {
  default: <default slot>,
  icon: <icon slot>,
  button: <button slot>,
};
Copy after login

But this $slots object only applies to the slots of the component, and Not every defined slot.

Take this component that defines several slots as an example, including several named slots:

<!-- Slots.vue -->
<template>
  <div>
    <h2>这里是一些slots</h2>
    <slot />
    <slot name="second" />
    <slot name="third" />
  </div>
</template>
Copy after login

If we only apply one slot to the component, only that slot will appear in our $slotsIn the object:

<template>
  <Slots>
    <template #second>
      这将应用于第二个slot
    </template>
  </Slots>
</template>
Copy after login
$slots = { second: <vnode> }
Copy after login

We can use this in our component to detect which slots have been applied to the component, for example, by hiding the slot's wrapping element:

<template>
  <div>
    <h2>一个包裹的slot</h2>
    <div v-if="$slots.default" class="styles">
      <slot />
    </div>
  </div>
</template>
Copy after login

Nowdiv, the wrapper to which the style is applied will only render if we actually fill the slot with something.

If we don't use v-if, divif we don't have slot, we will end up with an empty and unnecessary one. Depending on the style the #div has, this could mess up our layout and make things look weird.

Why do we want to conditionally render slots?

There are three main reasons for using conditional slots:

  • When using wrapper

    div to add default styles

  • slot is empty

  • When we combine default content with nested slots

For example, when we add a default style , we would add a

div around the slot:

<template>
  <div>
    <h2>This is a pretty great component, amirite?</h2>
    <div class="default-styling">
      <slot >
    </div>
    <button @click="$emit(&#39;click&#39;)">Click me!</button>
  </div>
</template>
Copy after login

However, if the parent component did not apply content to the slot, we would end up rendering on the page

div An empty:

<div>
  <h2>这是一个非常棒的组件</h2>
  <div class="default-styling">
    <!-- slot中没有内容,但仍会呈现此 div-->
  </div>
  <button @click="$emit(&#39;click&#39;)">Click me!</button>
</div>
Copy after login

v-if Adding it on the wrapper div solves the problem. Content not applied to slot? Like this:

<div>
  <h2>这是一个非常棒的组件</h2>
  <button @click="$emit(&#39;click&#39;)">Click me!</button>
</div>
Copy after login

7. How to observe changes in slot

Sometimes we need to know when the content in the slot has changed:

<!-- 可惜这个活动不存在 -->
<slot @change="update" />
Copy after login

Unfortunately, Vue has no built-in method To detect this, a very neat way is to use the mutation observer:

export default {
  mounted() {
    // 当事情发生变化时调用`update`
    const observer = new MutationObserver(this.update);

    // 观察这个组件的变化
    observer.observe(this.$el, {
      childList: true,
      subtree: true
    });
  }
};
Copy after login

8. Mixing local and global styles together

Often when using styles we want them to be qualified as Single component:

<style scoped>
  .component {
    background: green;
  }
</style>
Copy after login

If you need, you can also add a non-scoped style block to add global styles:

<style>
  /*全局应用*/
  .component p {
    margin-bottom: 16px;
  }
</style>

<style scoped>
  /*范围限定于此特定组件*/
  .component {
    background: green;
  }
</style>
Copy after login

9. 覆盖子组件的样式——正确的方法

Scoped CSS 比较容易保持整洁,并且不会意外地将样式渗入应用程序的其他部分。但有时你需要覆盖子组件的样式,并突破该范围。Vue 有一个deep专门用于此的选择器:

<style scoped>
/* 覆盖子组件的 CSS,同时保持样式范围*/
.my-component >>> .child-component {
  font-size: 24px;
}
</style>
Copy after login

注意:如果你使用的是 SCSS 之类的 CSS 预处理器,则可能需要改用/deep/

10. 用上下文感知组件创造魔法

上下文感知组件是“神奇的”——它们可以自动适应周围发生的事情,处理边缘情况,状态共享等等。有 3 种主要类型的上下文感知组件,但是我觉得配置是其中最有趣的一种。

1. 状态共享

当你将一个大组件分解成多个小组件时,它们通常仍然需要共享状态。你可以“在幕后”实现这一点,而不是将这项工作推给使用组件的人。

可以将一个Dropdown组件分解为SelectOption组件以提供更大的灵活性。但是为了更容易使用,SelectOption组件彼此共享selected状态:

<!-- 为简单起见用作单个组件 -->
<Dropdown v-model="selected" :options="[]" />

<!-- 拆分以获得更大的灵活性 -->
<Select v-model="selected">
  <Option value="mustard">Mustard</Option>
  <Option value="ketchup">Ketchup</Option>
  <div class="relish-wrapper">
    <Option value="relish">Relish</Option>
  </div>
</Select>
Copy after login

2. 配置

有时需要根据应用程序其余部分的情况更改组件的行为。这样做通常是为了自动处理边缘情况,否则会很麻烦。Popup或者Tooltip应该重新定位自己,这样它就不会溢出页面。但是,如果该组件位于 modal 内部,则它应该重新定位自身,以免溢出modal。如果Tooltip知道它何时在模态内,这可以自动完成。

3. 造型

当你创建了上下文感知 CSS,根据父元素或兄弟元素中发生的情况应用不同的样式。

.statistic {
  color: black;
  font-size: 24px;
  font-weight: bold;
}

/* 在彼此相邻的统计数据之间进行一些分离*/
.statistic + .statistic {
  margin-left: 10px;
}
Copy after login

CSS 中变量让我们更进一步允许我们在页面的不同部分设置不同的值。

11. 如何使在 Vue 之外创建的变量具有响应性?

如果你从 Vue 外部获得一个变量,那么能够使其具有响应性就很好。这样你就可以在计算道具、观察者和其他任何地方使用它,它就像 Vue 中的任何其他状态一样工作。

当你正在使用 options API,你只需将它放在data组件的部分中:

const externalVariable = getValue();

export default {
  data() {
    return {
      reactiveVariable: externalVariable,
    };
  }
};
Copy after login

当你在 Vue 3 中使用组合 API,则可以使用refreactive这样:

import { ref } from &#39;vue&#39;;

// 可以完全在 Vue 组件之外完成
const externalVariable = getValue();
const reactiveVariable = ref(externalVariable);

// 使用 .value 访问
console.log(reactiveVariable.value);
Copy after login

使用reactive来代替:

import { reactive } from &#39;vue&#39;;

// 可以完全在 Vue 组件之外完成
const externalVariable = getValue();
// Reactive 仅适用于对象和数组
const anotherReactiveVariable = reactive(externalVariable);

// 直接访问
console.log(anotherReactiveVariable);
Copy after login

如果你仍在使用 Vue 2(就像我们中的许多人一样),你可以使用observable而不是reactive获得完全相同的结果。

12. 在 v-for 中解构

你知道你可以在 v-for 中解构吗?

<li
  v-for="{ name, id } in users"
  :key="id"
>
  {{ name }}
</li>
Copy after login

众所周知,你可以使用这样的元组从 v-for 中获取索引:

<li v-for="(value, key) in [
  &#39;Hai Yong&#39;,
  &#39;Frozen&#39;,
  &#39;Web Beginner&#39;
]">
  {{ index + 1 }} - {{ value }}
</li>
Copy after login

使用对象时,你还可以抓住key:

<li v-for="(value, key) in {
  name: &#39;Hai Yong&#39;,
  released: 2021,
  director: &#39;A blogger&#39;,
}">
  {{ key }}: {{ value }}
</li>
Copy after login

也可以结合这两种方法,获取属性的键和索引:

<li v-for="(value, key, index) in {
  name: &#39;Hai Yong&#39;,
  released: 2021,
  director: &#39;A blogger&#39;,
}">
  #{{ index + 1 }}. {{ key }}: {{ value }}
</li>
Copy after login

13. 在 Vue 中循环一个范围

v-for指令允许我们遍历一个数组,但它也让我们遍历一个范围:

<template>
  <ul>
    <li v-for="n in 5">项目#{{ n }}</li>
  </ul>
</template>
Copy after login

显示效果:

  • 项目#1
  • 项目#2
  • 项目#3
  • 项目#4
  • 项目#5

当我们使用v-for范围时,它将从 1 开始并以我们指定的数字结束。

14. 观察组件中的任何内容

你的组件中的任何响应都可以被观察到:

export default {
  computed: {
    someComputedProperty() {
      // 更新计算道具
    },
  },
  watch: {
    someComputedProperty() {
      // 当计算的 prop 更新时做一些事情
    }
  }
};
Copy after login

你可以看:

  • 计算道具
  • 道具
  • 嵌套值

如果你使用组合 API,只要它是一个refreactive对象就可以监视任何值,。

15. 窃取道具类型

从子组件复制 prop 类型,只是为了在父组件中使用它们。但窃取这些道具类型比只是复制它们要好得多。

例如,我们Icon在这个组件中使用了一个组件:

<template>
  <div>
    <h2>{{ heading }}</h2>
    <Icon
      :type="iconType"
      :size="iconSize"
      :colour="iconColour"
    />
  </div>
</template>
Copy after login

为了让它工作,我们需要添加正确的道具类型,从Icon组件中复制:\

import Icon from &#39;./Icon&#39;;
export default {
  components: { Icon },
  props: {
    iconType: {
      type: String,
      required: true,
    },
    iconSize: {
      type: String,
      default: &#39;medium&#39;,
      validator: size => [
        &#39;small&#39;,
        &#39;medium&#39;,
        &#39;large&#39;,
        &#39;x-large&#39;
      ].includes(size),
    },
    iconColour: {
      type: String,
      default: &#39;black&#39;,
    },
    heading: {
      type: String,
      required: true,
    },
  },
};
Copy after login

Icon组件的 prop 类型更新时,你肯定你会忘记回到这个组件并更新它们。随着时间的推移,随着该组件的 prop 类型开始偏离组件中的 prop 类型,将引入错误Icon

所以这就是为什么我们会窃取它们:

import Icon from &#39;./Icon&#39;;
export default {
  components: { Icon },
  props: {
    ...Icon.props,
    heading: {
      type: String,
      required: true,
    },
  },
};
Copy after login

除了在我们的示例中,我们在每个道具名称的开头添加了“icon”。所以我们必须做一些额外的工作来实现这一点:

import Icon from &#39;./Icon&#39;;

const iconProps = {};

// Do some processing beforehand
Object.entries(Icon.props).forEach((key, val) => {
  iconProps[`icon${key[0].toUpperCase()}${key.substring(1)}`] = val;
});

export default {
  components: { Icon },
  props: {
    ...iconProps,
    heading: {
      type: String,
      required: true,
    },
  },
};
Copy after login

现在,如果Icon组件中的 prop 类型被修改,我们的组件将保持最新。

但是如果在Icon组件中添加或删除了一个 prop 类型呢?为了涵盖这些情况,我们可以使用v-bind计算道具来保持动态。

16. 检测元素外部(或内部)的点击

有时我们需要检测点击是发生在特定元素el内部还是外部。这是我们通常使用的方法:

window.addEventListener(&#39;mousedown&#39;, e => {
  // 获取被点击的元素
  const clickedEl = e.target;

  // `el` 是你正在检测外部点击的元素
  if (el.contains(clickedEl)) {
    // 单击“el”内部
  } else {
    // 在`el`之外点击
  }
});
Copy after login

17. 递归槽

我们是否可以v-for只使用模板来制作一个组件?在此过程中,我发现了如何递归地使用slot。

这是组件的样子:

<!-- VFor.vue -->
<template>
    <div>
        <!-- 渲染第一项 -->
    {{ list[0] }}
        <!-- 如果我们有更多的项目可以继续,但需要离开我们刚刚渲染的项目 -->
    <v-for
      v-if="list.length > 1"
            :list="list.slice(1)"
        />
    </div>
</template>
Copy after login

如果你想用作用域slot来做这件事——为什么不呢?!— 只需要进行一些调整:

<template>
  <div>
    <!-- 将项目传递到要渲染的slot中 -->
    <slot v-bind:item="list[0]">
      <!-- Default -->
      {{ list[0] }}
    </slot>

    <v-for
      v-if="list.length > 1"
      :list="list.slice(1)"
    >
      <!-- 递归向下传递作用域slot -->
      <template v-slot="{ item }">
        <slot v-bind:item="item" />
      </template>
    </v-for>
  </div>
</template>
Copy after login

以下是该组件的使用方法:

<template>
  <div>
    <!-- 常规列表 -->
    <v-for :list="list" />

    <!-- 带有粗体项目的列表 -->
    <v-for :list="list">
      <template v-slot="{ item }">
        <strong>{{ item }}</strong>
      </template>
    </v-for>
  </div>
</template>
Copy after login

18. 组件元数据

并不是你添加到组件的每一点信息都是状态。有时你需要添加一些元数据来为其他组件提供更多信息。

例如,如果你要为 Google Analytics 等分析仪表板构建一堆不同的小部件:

25 Vue tips you deserve to know

如果你希望布局知道每个小部件应占用多少列,你可以将其作为元数据直接添加到组件上:

export default {
  name: &#39;LiveUsersWidget&#39;,
  // ? 只需将其添加为额外属性
  columns: 3,
  props: {
    // ...
  },
  data() {
    return {
      //...
    };
  },
};
Copy after login

你会发现此元数据是组件上的一个属性:

import LiveUsersWidget from &#39;./LiveUsersWidget.vue&#39;;
const { columns } = LiveUsersWidget;
Copy after login

你还可以通过特殊$options属性从组件内部访问元数据:

export default {
  name: &#39;LiveUsersWidget&#39;,
  columns: 3,
  created() {
    // `$options` 包含组件的所有元数据
    console.log(`Using ${this.$options.metadata} columns`);
  },
};
Copy after login

请记住,此元数据对于组件的每个实例都是相同的,并且不是响应式的。

其他用途包括(但不限于):

  • 保留各个组件的版本号
  • 用于构建工具的自定义标志以区别对待组件
  • 向组件添加自定义功能,超出计算道具、数据、观察者等。

19. 多文件单文件组件

这是 SFC 的一个鲜为人知的功能。你可以像使用常规 HTML 文件一样导入文件:

<!-- "single" 文件组件 -->
<template src="./template.html"></template>
<script src="./script.js"></script>
<style scoped src="./styles.css"></style>
Copy after login

如果你需要共享样式、文档或其他任何内容这会很方便。也非常适合那些因滚动而磨损手指的超长组件文件

20. 可重用组件不是你想的那样

可重用组件不一定是大的或复杂的东西,我经常使小而短的组件可重复使用。因为我不会到处重写这段代码,更新它变得容易得多,而且我可以确保每个OverflowMenu看起来和工作完全一样——因为它们是一样的!

<!-- OverflowMenu.vue -->
<template>
  <Menu>
    <!-- 添加自定义按钮来触发我们的菜单 -->
    <template #button v-slot="bind">
      <!-- 使用 bind 传递点击处理程序、a11y 属性等。 -->
      <Button v-bind="bind">
        <!-- 使用我们自己的“...”图标,此按钮没有文字 -->
        <template #icon>
          <svg src="./ellipsis.svg" />
        </template>
      </Button>
    </template>
  </Menu>
</template>
Copy after login

这里我们使用了一个Menu组件,但是在触发它打开的按钮上添加了一个“...”(省略号)图标。可能并不不值得用它来制作可重用的组件,因为它只有几行。每次我们想使用Menu这样的时候,我们不能只添加图标吗?但这OverflowMenu将使用数十次,现在如果我们想要更新图标或其行为,我们可以很容易地做到。而且使用起来也简单多了!

<template>
  <OverflowMenu
    :menu-items="items"
    @click="handleMenuClick"
  />
</template>
Copy after login

21. 从组件外部调用方法

你可以通过给它一个从组件外部调用方法ref

<!-- Parent.vue -->
<template>
  <ChildComponent ref="child" />
</template>
Copy after login
// Parent.vue 中的某个地方
this.$refs.child.method();
Copy after login
Copy after login

通常,我们使用道具和事件在组件之间进行通信。道具被发送到子组件,事件被发送回父组件。

<template>
  <ChildComponent
    :tell-me-what-to-do="someInstructions"
    @something-happened="hereIWillHelpYouWithThat"
  />
</template>
Copy after login

但有时你可能会遇到需要父组件触发子组件中的方法的情况。这是只有向下传递道具不起作用的地方。可以向下传递一个布尔值并让子组件监视它:

<!-- Parent.vue -->
<template>
  <ChildComponent :trigger="shouldCallMethod" />
</template>
Copy after login
// Child.vue
export default {
  props: [&#39;trigger&#39;],
  watch: {
    shouldCallMethod(newVal) {
      if (newVal) {
        // 当触发器设置为 `true` 时调用该方法
        this.method();
      }
    }
  }
}
Copy after login

这工作正常,但仅限于第一次调用。如果你需要多次触发此操作,则必须清理并重置状态。然后逻辑看起来像这样:

  • Parent 组件传递truetriggerprop

  • Watch被触发,Child组件调用方法

  • Child 组件发出一个事件告诉 Parent 组件该方法已成功触发

  • Parent 组件重置triggerfalse,因此我们可以再次执行此操作

啊。

相反,如果我们在子组件上设置ref ,我们可以直接调用该方法:

<!-- Parent.vue -->
<template>
  <ChildComponent ref="child" />
</template>
Copy after login
// Parent.vue 中的某个地方
this.$refs.child.method();
Copy after login
Copy after login

我们打破了“props down, events up”规则,打破了封装,但它更清晰、更容易理解值得这样做!

有时,“最佳”解决方案最终会成为最差的解决方案。

22. 观察数组和对象

使用观察者最棘手的部分是有时候它似乎不能正确触发。一般都是因为你试图查看一个数组或一个对象,但没有设置deeptrue

export default {
  name: &#39;ColourChange&#39;,
  props: {
    colours: {
      type: Array,
      required: true,
    },
  },
  watch: {
    // 使用对象语法而不仅仅是方法
    colours: {
      // 这将让 Vue 知道查看数组内部
      deep: true,

      // 我们必须将我们的方法移动到处理程序字段
      handler()
        console.log(&#39;颜色列表已更改!&#39;);
      }
    }
  }
}
Copy after login

使用 Vue 3 的反应式 API 看起来像这样:

watch(
  colours,
  () => {
    console.log(&#39;颜色列表已更改!&#39;);
  },
  {
    deep: true,
  }
);
Copy after login

如果你想了解更多信息,可以参阅Vue 3Vue 2的文档。

23. 与 Vue Router 深度链接

你可以在 URL 中存储(一些)状态,允许你直接跳转到页面上的特定状态。

比如你可以加载一个已选择日期范围过滤器的页面:

someurl.com/edit?date-range=last-week
Copy after login

这对于用户可能共享大量链接的应用程序部分、服务器呈现的应用程序或在两个独立应用程序之间传递比常规链接通常提供的信息更多的信息非常有用。

你可以存储过滤器、搜索值、模式是打开还是关闭,或者我们滚动到的列表中的位置——非常适合无限分页。

使用vue-router这样的方式获取查询(这也适用于 Nuxt 和 Vuepress 等大多数 Vue 框架):

const dateRange = this.$route.query.dateRange;
Copy after login

要更改它,我们使用RouterLink组件并更新query

<RouterLink :to="{
  query: {
    dateRange: newDateRange
  }
}">
Copy after login

24. 模板标签的另一种用途

template标签可以在模板内的任何地方使用,以更好地组织代码。

我喜欢用它来简化v-if逻辑,有时v-for也是。

在这个例子中,我们有几个元素都使用相同的v-if条件:\

<template>
  <div class="card">
    <img  src="imgPath" / alt="25 Vue tips you deserve to know" >
    <h3>
      {{ title }}
    </h3>
    <h4 v-if="expanded">
      {{ subheading }}
    </h4>
    <div v-if="expanded" class="card-content">
      <slot/>
    </div>
    <SocialShare v-if="expanded" />
  </div>
</template>
Copy after login

这有点笨拙,一开始并不明显,一堆这些元素被显示和隐藏在一起。在更大、更复杂的组件上,这可能是更糟糕的情况!

但我们可以解决这个问题。

我们可以使用template标签对这些元素进行分组,并将其提升v-iftemplate标签本身:\

<template>
  <div class="card">
    <img  src="imgPath" / alt="25 Vue tips you deserve to know" >
    <h3>
      {{ title }}
    </h3>
    <template v-if="expanded">
      <h4>
        {{ subheading }}
      </h4>
      <div class="card-content">
        <slot/>
      </div>
      <SocialShare/>
    </template>
  </div>
</template>
Copy after login

25. 处理错误(和警告)的更好方法

你可以为 Vue 中的错误和警告提供自定义处理程序:

// Vue 3
const app = createApp(App);
app.config.errorHandler = (err) => {
  alert(err);
};

// Vue 2
Vue.config.errorHandler = (err) => {
  alert(err);
};
Copy after login

Bugsnag 和 Rollbar 等错误跟踪服务挂接到这些处理程序中以记录错误,但你也可以使用它们来更优雅地处理错误以获得更好的用户体验。

例如,如果错误未得到处理,应用程序不仅会崩溃,还可以显示整页错误屏幕并让用户刷新或尝试其他操作。

在 Vue 3 中,错误处理程序仅适用于模板和观察程序错误,但 Vue 2 错误处理程序几乎可以捕获所有内容。两个版本中的警告处理程序仅适用于开发。

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

The above is the detailed content of 25 Vue tips you deserve to know. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
vue
source:juejin.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template