Apakah yang digunakan tindak balas untuk mengurus keadaan?

青灯夜游
Lepaskan: 2022-03-22 16:12:24
asal
2849 orang telah melayarinya

Alat pengurusan keadaan bertindak balas: 1. Gunakan cangkuk untuk pengurusan negeri 2. Gunakan Redux untuk pengurusan negeri Kaedah ini mempunyai set alat sokongan yang agak lengkap dan boleh menyesuaikan pelbagai perisian tengah; Pengurusan negeri, yang menjadikan pengurusan negeri mudah dan berskala melalui pengaturcaraan reaktif berfungsi yang telus.

Apakah yang digunakan tindak balas untuk mengurus keadaan?

Persekitaran pengendalian tutorial ini: sistem Windows 7, bertindak balas versi 17.0.1, komputer Dell G3.

Apakah itu "status"?

Dalam era jQuery, struktur DOM bercampur dalam kod JS, dan apabila pelbagai proses menjadi rumit dan saling berkaitan, kod spageti terbentuk Apabila menggunakan model publish-subscribe, penyahpepijatan akan menjadi kucar-kacir.

jQuery ialah pengaturcaraan penting untuk "proses", dan begitu banyak arahan akhirnya digunakan untuk mengemas kini "data" dalam UI Mengapa tidak menukar data secara terus?

Beijing → Shanghai, cuma tukar bandar="Beijing" kepada bandar="Shanghai". Tidak kira sama ada kapal terbang atau kereta api rosak dengan berjalan kaki, atau sama ada anda akan bertemu Wang Baoqiang di jalan raya,

Kepentingan rangka kerja hadapan moden ialah inovasi idea penyelesaian masalah, menghidupkan pelbagai arahan "proses" menjadi "keadaan" " huraian.

Apakah status? Keadaan ialah data dinamik dalam UI.

Nyatakan dalam React

Mei 2013 React dilahirkan. Tetapi sebelum 2015, jQuery mungkin adalah dunia. React 0.13.0 telah dikeluarkan pada Mac 2015, membawa kaedah penulisan komponen kelas.

Dalam era komponen kelas React, keadaan ialah this.state, yang dikemas kini menggunakan this.setState.

Untuk mengelakkan kekacauan, React memperkenalkan konsep "komponen" dan "aliran data satu arah". Dengan keadaan dan komponen, secara semula jadi terdapat pemindahan keadaan antara komponen, yang biasanya dipanggil "komunikasi".

Komunikasi bapa-anak agak mudah, manakala komunikasi antara komponen peringkat dalam dan jarak jauh bergantung pada prop "promosi status" yang dilalui lapisan demi lapisan.

Hasilnya, React memperkenalkan Context, penyelesaian rasmi untuk menyelesaikan komunikasi "rentas peringkat" antara komponen.

Tetapi Konteks sebenarnya bersamaan dengan "peningkatan status", tidak ada pengoptimuman prestasi tambahan dan ia adalah kasar untuk ditulis.

Untuk mengoptimumkan prestasi, berbilang Konteks biasanya ditambah, yang menjadikan penulisan lebih bertele-tele. Apabila projek itu tidak begitu kompleks, ia tidak semudah melepasinya melalui lapisan.

Apakah itu "pengurusan negeri"?

Secara pragmatik, "pengurusan negeri" adalah untuk menyelesaikan komunikasi "rentas peringkat" antara komponen.

Sudah tentu, apabila menggunakan perpustakaan pengurusan negeri, ia akan membawa beberapa mod pemikiran terbitan, seperti cara mengatur keadaan, cara memecah logik awam, logik perniagaan, logik komponen, dll., tetapi pada akhirnya analisis, ini bukan sebab utama.

Intinya adalah untuk menyelesaikan masalah praktikal - untuk komunikasi. Pelbagai konsep dan falsafah lain tidak perlu.

Konteks tidak begitu mudah untuk digunakan dan tiada amalan terbaik rasmi untuk React, jadi perpustakaan komuniti lahir satu demi satu.

Kaedah pengurusan negeri secara bertindak balas

Pada masa ini, terdapat tiga kaedah pengurusan negeri yang biasa digunakan: cangkuk, redux dan mobx Di bawah saya akan memperkenalkan secara terperinci cara menggunakan ketiga-tiga jenis ini dan menganalisisnya masing-masing Kelebihan dan kekurangan adalah untuk rujukan anda.

Pengurusan negeri Hooks

Terdapat dua cara utama untuk menggunakan cangkuk untuk pengurusan negeri:

  • useContext useReducer
  • useState useEffect

useContext useReducer

Penggunaan

1 Cipta kedai dan pengurang serta konteks global

src/store/reducer.ts

<.>
import React from "react";
// 初始状态
export const state = {
  count: 0,
  name: "ry",
};

// reducer 用于修改状态
export const reducer = (state, action) => {
  const { type, payload } = action;
  switch (type) {
    case "ModifyCount":
      return {
        ...state,
        count: payload,
      };
    case "ModifyName":
      return {
        ...state,
        name: payload,
      };
    default: {
      return state;
    }
  }
};

export const GlobalContext = React.createContext(null);
Salin selepas log masuk

2. Komponen akar menyuntik konteks melalui Pembekal

src/App.tsx

import React, { useReducer } from "react";
import &#39;./index.less&#39;
import { state as initState, reducer, GlobalContext} from &#39;./store/reducer&#39;
import Count from &#39;./components/Count&#39;
import Name from &#39;./components/Name&#39;

export default function () {
  const [state, dispatch] = useReducer(reducer, initState);

  return (
    <div>
      <GlobalContext.Provider value={{state, dispatch}}>
        <Count />
        <Name />
      </GlobalContext.Provider>
    </div>
  )
}
Salin selepas log masuk

3. Gunakan

src/components/Count/index.tsx

import { GlobalContext } from "@/store/reducer";
import React, { FC, useContext } from "react";

const Count: FC = () => {
  const ctx = useContext(GlobalContext)
  return (
    <div>
      <p>count:{ctx.state.count}</p>
      <button onClick={() => ctx.dispatch({ type: "ModifyCount", payload: ctx.state.count+1 })}>+1</button>
    </div>
  );
};

export default Count;
Salin selepas log masuk
src/components/Name/index.tsx

import { GlobalContext } from "@/store/reducer";
import React, { FC, useContext } from "react";

const Name: FC = () => {
  const ctx = useContext(GlobalContext)
  console.log("NameRerendered")
  return (
    <div>
      <p>name:{ctx.state.name}</p>
    </div>
  );
};

export default Name;
Salin selepas log masuk

useStat useEffect

Penggunaan

1 Cipta keadaan dan pengurang

src/global-states.ts

// 初始state
let globalState: GlobalStates = {
  count: 0,
  name: &#39;ry&#39;
}

// reducer
export const modifyGlobalStates = (
  operation: GlobalStatesModificationType,
  payload: any
) => {
  switch (operation) {
    case GlobalStatesModificationType.MODIFY_COUNT:
      globalState = Object.assign({}, globalState, { count: payload })
      break
    case GlobalStatesModificationType.MODIFY_NAME:
      globalState = Object.assign({}, globalState, { name: payload })
      break
  }
  broadcast()
}
Salin selepas log masuk
src/global-states.type.ts

export interface GlobalStates {
  count: number;
  name: string;
}

export enum GlobalStatesModificationType {
  MODIFY_COUNT,
  MODIFY_NAME
}
Salin selepas log masuk

2.写一个发布订阅模式,让组件订阅globalState

src/global-states.ts

import { useState, useEffect } from &#39;react&#39;
import {
  GlobalStates,
  GlobalStatesModificationType
} from &#39;./global-states.type&#39;

let listeners = []

let globalState: GlobalStates = {
  count: 0,
  name: &#39;ry&#39;
}
// 发布,所有订阅者收到消息,执行setState重新渲染
const broadcast = () => {
  listeners.forEach((listener) => {
    listener(globalState)
  })
}

export const modifyGlobalStates = (
  operation: GlobalStatesModificationType,
  payload: any
) => {
  switch (operation) {
    case GlobalStatesModificationType.MODIFY_COUNT:
      globalState = Object.assign({}, globalState, { count: payload })
      break
    case GlobalStatesModificationType.MODIFY_NAME:
      globalState = Object.assign({}, globalState, { name: payload })
      break
  }
  // 状态改变即发布
  broadcast()
}

// useEffect + useState实现发布订阅
export const useGlobalStates = () => {
  const [value, newListener] = useState(globalState)

  useEffect(() => {
    // newListener是新的订阅者
    listeners.push(newListener)
    // 组件卸载取消订阅
    return () => {
      listeners = listeners.filter((listener) => listener !== newListener)
    }
  })

  return value
}
Salin selepas log masuk

3.组件中使用

src/App.tsx

import React from &#39;react&#39;
import &#39;./index.less&#39;
import Count from &#39;./components/Count&#39;
import Name from &#39;./components/Name&#39;

export default function () {
  return (
    <div>
      <Count />
      <Name />
    </div>
  )
}
Salin selepas log masuk

src/components/Count/index.tsx

import React, { FC } from &#39;react&#39;
import { useGlobalStates, modifyGlobalStates } from &#39;@/store/global-states&#39;
import { GlobalStatesModificationType } from &#39;@/store/global-states.type&#39;

const Count: FC = () => {
  // 调用useGlobalStates()即订阅globalStates()
  const { count } = useGlobalStates()
  return (
    <div>
      <p>count:{count}</p>
      <button
        onClick={() =>
          modifyGlobalStates(
            GlobalStatesModificationType.MODIFY_COUNT,
            count + 1
          )
        }
      >
        +1
      </button>
    </div>
  )
}

export default Count
Salin selepas log masuk

src/components/Name/index.tsx

import React, { FC } from &#39;react&#39;
import { useGlobalStates } from &#39;@/store/global-states&#39;

const Count: FC = () => {
  const { name } = useGlobalStates()
  console.log(&#39;NameRerendered&#39;)
  return (
    <div>
      <p>name:{name}</p>
    </div>
  )
}

export default Count
Salin selepas log masuk

优缺点分析

由于以上两种都是采用hooks进行状态管理,这里统一进行分析

优点

  • 代码比较简洁,如果你的项目比较简单,只有少部分状态需要提升到全局,大部分组件依旧通过本地状态来进行管理。这时,使用 hookst进行状态管理就挺不错的。杀鸡焉用牛刀。

缺点

  • 两种hooks管理方式都有一个很明显的缺点,会产生大量的无效rerender,如上例中的Count和Name组件,当state.count改变后,Name组件也会rerender,尽管他没有使用到state.count。这在大型项目中无疑是效率比较低的。

Redux状态管理

使用方法:

1.引入redux

yarn add redux react-redux @types/react-redux redux-thunk
Salin selepas log masuk

2.新建reducer

在src/store/reducers文件夹下新建addReducer.ts(可建立多个reducer)

import * as types from &#39;../action.types&#39;
import { AnyAction } from &#39;redux&#39;

// 定义参数接口
export interface AddState {
  count: number
  name: string
}

// 初始化state
let initialState: AddState = {
  count: 0,
  name: &#39;ry&#39;
}

// 返回一个reducer
export default (state: AddState = initialState, action: AnyAction): AddState => {
  switch (action.type) {
    case types.ADD:
      return { ...state, count: state.count + action.payload }
    default:
      return state
  }
}
Salin selepas log masuk

在src/stores文件夹下新建action.types.ts
主要用于声明action类型

export const ADD = &#39;ADD&#39;
export const DELETE = &#39;DELETE&#39;
Salin selepas log masuk

3.合并reducer

在src/store/reducers文件夹下新建index.ts

import { combineReducers, ReducersMapObject, AnyAction, Reducer } from &#39;redux&#39;
import addReducer, { AddState } from &#39;./addReducer&#39;

// 如有多个reducer则合并reducers,模块化
export interface CombinedState {
  addReducer: AddState
}
const reducers: ReducersMapObject<CombinedState, AnyAction> = {
  addReducer
}
const reducer: Reducer<CombinedState, AnyAction> = combineReducers(reducers)

export default reducer
Salin selepas log masuk

3.创建store

在src/stores文件夹下新建index.ts

import {
  createStore,
  applyMiddleware,
  StoreEnhancer,
  StoreEnhancerStoreCreator,
  Store
} from &#39;redux&#39;
import thunk from &#39;redux-thunk&#39;
import reducer from &#39;./reducers&#39;

// 生成store增强器
const storeEnhancer: StoreEnhancer = applyMiddleware(thunk)
const storeEnhancerStoreCreator: StoreEnhancerStoreCreator = storeEnhancer(createStore)

const store: Store = storeEnhancerStoreCreator(reducer)

export default store
Salin selepas log masuk

4.根组件通过 Provider 注入 store

src/index.tsx(用provider将App.tsx包起来)

import React from &#39;react&#39;
import ReactDOM from &#39;react-dom&#39;
import App from &#39;./App&#39;
import { Provider } from &#39;react-redux&#39;
import store from &#39;./store&#39;

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById(&#39;root&#39;)
)
Salin selepas log masuk

5.在组件中使用

src/somponents/Count/index.tsx

import React, { FC } from &#39;react&#39;
import { connect } from &#39;react-redux&#39;
import { Dispatch } from &#39;redux&#39;
import { AddState } from &#39;src/store/reducers/addReducer&#39;
import { CombinedState } from &#39;src/store/reducers&#39;
import * as types from &#39;@/store/action.types&#39;

// 声明参数接口
interface Props {
  count: number
  add: (num: number) => void
}

// ReturnType获取函数返回值类型,&交叉类型(用于多类型合并)
// type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>

const Count: FC<Props> = (props) => {
  const { count, add } = props
  return (
    <div>
      <p>count: {count}</p>
      <button onClick={() => add(5)}>addCount</button>
    </div>
  )
}

// 这里相当于自己手动做了映射,只有这里映射到的属性变化,组件才会rerender
const mapStateToProps = (state: CombinedState) => ({
  count: state.addReducer.count
})

const mapDispatchToProps = (dispatch: Dispatch) => {
  return {
    add(num: number = 1) {
      // payload为参数
      dispatch({ type: types.ADD, payload: num })
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Count)
Salin selepas log masuk

src/somponents/Name/index.tsx

import React, { FC } from &#39;react&#39;
import { connect } from &#39;react-redux&#39;
import { Dispatch } from &#39;redux&#39;
import { AddState } from &#39;src/store/reducers/addReducer&#39;
import { CombinedState } from &#39;src/store/reducers&#39;
import * as types from &#39;@/store/action.types&#39;

// 声明参数接口
interface Props {
  name: string
}

const Name: FC<Props> = (props) => {
  const { name } = props
  console.log(&#39;NameRerendered&#39;)
  return (
    <div>
      <p>name: {name}</p>
    </div>
  )
}

// name变化组件才会rerender
const mapStateToProps = (state: CombinedState) => ({
  name: state.addReducer.name
})

// addReducer内任意属性变化组件都会rerender
// const mapStateToProps = (state: CombinedState) => state.addReducer

export default connect(mapStateToProps)(Name)
Salin selepas log masuk

优缺点分析

优点

  • 组件会订阅store中具体的某个属性【mapStateToProps手动完成】,只要当属性变化时,组件才会rerender,渲染效率较高
  • 流程规范,按照官方推荐的规范和结合团队风格打造一套属于自己的流程。
  • 配套工具比较齐全redux-thunk支持异步,redux-devtools支持调试
  • 可以自定义各种中间件

缺点

  • state+action+reducer的方式不太好理解,不太直观
  • 非常啰嗦,为了一个功能又要写reducer又要写action,还要写一个文件定义actionType,显得很麻烦
  • 使用体感非常差,每个用到全局状态的组件都得写一个mapStateToProps和mapDispatchToProps,然后用connect包一层,我就简单用个状态而已,咋就这么复杂呢
  • 当然还有一堆的引入文件,100行的代码用了redux可以变成120行,不过换个角度来说这也算增加了自己的代码量
  • 好像除了复杂也没什么缺点了

Mobx状态管理

MobX 是一个经过战火洗礼的库,它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。

常规使用(mobx-react)

使用方法

1.引入mobx

yarn add mobx mobx-react -D
Salin selepas log masuk

2.创建store

在/src/store目录下创建你要用到的store(在这里使用多个store进行演示)
例如:
store1.ts

import { observable, action, makeObservable } from &#39;mobx&#39;

class Store1 {
  constructor() {
    makeObservable(this) //mobx6.0之后必须要加上这一句
  }
  @observable
  count = 0

  @observable
  name = &#39;ry&#39;

  @action
  addCount = () => {
    this.count += 1
  }
}

const store1 = new Store1()
export default store1
Salin selepas log masuk

store2.ts
这里使用 makeAutoObservable代替了makeObservable,这样就不用对每个state和action进行修饰了(两个方法都可,自行选择)

import { makeAutoObservable } from &#39;mobx&#39;

class Store2 {
  constructor() {
    // mobx6.0之后必须要加上这一句
    makeAutoObservable(this)
  }
  time = 11111111110
}

const store2 = new Store2()
export default store2
Salin selepas log masuk

3.导出store

src/store/index.ts

import store1 from &#39;./store1&#39;
import store2 from &#39;./store2&#39;

export const store = { store1, store2 }
Salin selepas log masuk

4.根组件通过 Provider 注入 store

src/index.tsx(用provider将App.tsx包起来)

import React from &#39;react&#39;
import ReactDOM from &#39;react-dom&#39;
import App from &#39;./App&#39;
import store from &#39;./store&#39;
import { Provider } from &#39;mobx-react&#39;

ReactDOM.render(
  <Provider {...store}>
    <App />
  </Provider>,
  document.getElementById(&#39;root&#39;)
)
Salin selepas log masuk

5.在组件中使用

src/somponents/Count/index.tsx

import React, { FC } from &#39;react&#39;
import { observer, inject } from &#39;mobx-react&#39;

// 类组件用装饰器注入,方法如下
// @inject(&#39;store1&#39;)
// @observer
interface Props {
  store1?: any
}
const Count: FC<Props> = (props) => {
  const { count, addCount } = props.store1
  return (
    <div>
      <p>count: {count}</p>
      <button onClick={addCount}>addCount</button>
    </div>
  )
}
// 函数组件用Hoc,方法如下(本文统一使用函数组件)
export default inject(&#39;store1&#39;)(observer(Count))
Salin selepas log masuk

src/components/Name/index.tsx

import React, { FC } from &#39;react&#39;
import { observer, inject } from &#39;mobx-react&#39;

interface Props {
  store1?: any
}

const Name: FC<Props> = (props) => {
  const { name } = props.store1
  console.log(&#39;NameRerendered&#39;)
  return (
    <div>
      <p>name: {name}</p>
    </div>
  )
}
// 函数组件用Hoc,方法如下(本文统一使用函数组件)
export default inject(&#39;store1&#39;)(observer(Name))
Salin selepas log masuk

优缺点分析:

优点:

  • 组件会自动订阅store中具体的某个属性,无需手动订阅噢!【下文会简单介绍下原理】只有当订阅的属性变化时,组件才会rerender,渲染效率较高
  • 一个store即写state,也写action,这种方式便于理解,并且代码量也会少一些

缺点:

  • 当我们选择的技术栈是React+Typescript+Mobx时,这种使用方式有一个非常明显的缺点,引入的store必须要在props的type或interface定义过后才能使用(会增加不少代码量),而且还必须指定这个store为可选的,否则会报错(因为父组件其实没有传递这个prop给子组件),这样做还可能会致使对store取值时,提示可能为undefined,虽然能够用“!”排除undefined,可是这种作法并不优雅。

最佳实践(mobx+hooks)

使用方法

1.引入mobx

同上

2.创建store

同上

3.导出store(结合useContext)

src/store/index.ts

import React from &#39;react&#39;
import store1 from &#39;./store1&#39;
import store2 from &#39;./store2&#39;

// 导出store1
export const storeContext1 = React.createContext(store1)
export const useStore1 = () => React.useContext(storeContext1)

// 导出store2
export const storeContext2 = React.createContext(store2)
export const useStore2 = () => React.useContext(storeContext2)
Salin selepas log masuk

4.在组件中使用

无需使用Provider注入根组件
src/somponents/Count/index.tsx

import React, { FC } from &#39;react&#39;
import { observer } from &#39;mobx-react&#39;
import { useStore1 } from &#39;@/store/&#39;

// 类组件可用装饰器,方法如下
// @observer

const Count: FC = () => {
  const { count, addCount } = useStore1()
  return (
    <div>
      <p>count: {count}</p>
      <button onClick={addCount}>addCount</button>
    </div>
  )
}
// 函数组件用Hoc,方法如下(本文统一使用函数组件)
export default observer(Count)
Salin selepas log masuk

src/components/Name/index.tsx

import React, { FC } from &#39;react&#39;
import { observer } from &#39;mobx-react&#39;
import { useStore1 } from &#39;@/store/&#39;

const Name: FC = () => {
  const { name } = useStore1()
  console.log(&#39;NameRerendered&#39;)
  return (
    <div>
      <p>name: {name}</p>
    </div>
  )
}

export default observer(Name)
Salin selepas log masuk

优缺点分析:

优点:

  • 学习成本少,基础知识非常简单,跟 Vue 一样的核心原理,响应式编程。
  • 一个store即写state,也写action,这种方式便于理解
  • 组件会自动订阅store中具体的某个属性,只要当属性变化时,组件才会rerender,渲染效率较高
  • 成功避免了上一种使用方式的缺点,不用对使用的store进行interface或type声明!
  • 内置异步action操作方式
  • 代码量真的很少,使用很简单有没有,强烈推荐!

缺点:

  • 过于自由:Mobx提供的约定及模版代码很少,这导致开发代码编写很自由,如果不做一些约定,比较容易导致团队代码风格不统一,团队建议启用严格模式!
  • 使用方式过于简单

Mobx自动订阅实现原理

基本概念

Observable  //被观察者,状态
Observer    //观察者,组件
Reaction    //响应,是一类的特殊的 Derivation,可以注册响应函数,使之在条件满足时自动执行。
Salin selepas log masuk

建立依赖

我们给组件包的一层observer实现了这个功能

export default observer(Name)
Salin selepas log masuk

组件每次mount和update时都会执行一遍useObserver函数,useObserver函数中通过reaction.track进行依赖收集,将该组件加到该Observable变量的依赖中(bindDependencies)。

// fn = function () { return baseComponent(props, ref); 
export function useObserver(fn, baseComponentName) {
    ...
    var rendering;
    var exception;
    reaction.track(function () {
        try {
            rendering = fn();
        }
        catch (e) {
            exception = e;
        }
    });
    if (exception) {
        throw exception; // re-throw any exceptions caught during rendering
    }
    return rendering;
}
Salin selepas log masuk

reaction.track()

 _proto.track = function track(fn) {
    // 开始收集
    startBatch();
    var result = trackDerivedFunction(this, fn, undefined);
    // 结束收集
    endBatch();
  };
Salin selepas log masuk

reaction.track里面的核心内容是trackDerivedFunction

function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
   	...
    let result

    // 执行回调f,触发了变量(即组件的参数)的 get,从而获取 dep【收集依赖】
    if (globalState.disableErrorBoundaries === true) {
        result = f.call(context)
    } else {
        try {
            result = f.call(context)
        } catch (e) {
            result = new CaughtException(e)
        }
    }
    globalState.trackingDerivation = prevTracking

    // 给 observable 绑定 derivation
    bindDependencies(derivation)
   ...
    return result
}
Salin selepas log masuk

触发依赖

Observable(被观察者,状态)修改后,会调用它的set方法,然后再依次执行该Observable之前收集的依赖函数,触发rerender。

组件更新

用组件更新来简单阐述总结一下:mobx的执行原理。

  • observer这个装饰器(也可以是Hoc),对React组件的render方法进行track。

  • 将render方法,加入到各个observable的依赖中。当observable发生变化,track方法就会执行。

  • track中,还是先进行依赖收集,调用forceUpdate去更新组件,然后结束依赖收集。

每次都进行依赖收集的原因是,每次执行依赖可能会发生变化。

总结

简单总结了一下目前较为常用的状态管理方式,我个人最喜欢的使用方式是Mobx+Hooks,简单轻量易上手。各位可以根据自己的需求选择适合自己项目的管理方式。

【相关推荐:Redis视频教程

Atas ialah kandungan terperinci Apakah yang digunakan tindak balas untuk mengurus keadaan?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Label berkaitan:
sumber:php.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan