目錄
專案描述
實作方案
建構小程式框架產物
React運行到小程式平台方案分析
编译时实现
运行时实现
总结
项目地址
首頁 微信小程式 小程式開發 React如何建立小程式?兩種實現方案分享

React如何建立小程式?兩種實現方案分享

Dec 23, 2021 am 10:27 AM
react 小程式

React如何建立小程式?以下這篇文章給大家透過1500行程式碼揭秘React如何運作到小程式平台,介紹一下React 建構小程式兩種實作方案,希望對大家有幫助!

React如何建立小程式?兩種實現方案分享

你是否使用過 TaroRemax 類似的框架?你是否想了解這類框架如何實作 React 程式碼運行到小程式平台?如果是的話,那麼也許你可以花喝一杯咖啡的時間繼續往下閱讀,本文將透過兩種方案實現 React 運行到小程式平台。如果你現在就想閱讀這1500行的實作程式碼,那麼可以直接點擊專案原始碼來取得(也許要多喝幾杯咖啡)。

專案描述

為了更清楚地描述實現過程,我們把實作方案當作一個專案來對待。
專案需求:使下列計數器功能的 React 程式碼運作到微信小程式平台。

import React, { Component } from 'react'
import { View, Text, Button } from '@leo/components'
import './index.css'

export default class Index extends Component {
  constructor() {
    super()
    this.state = { count: 0 }
    this.onAddClick = this.onAddClick.bind(this)
    this.onReduceClick = this.onReduceClick.bind(this)
  }
  componentDidMount () {
    console.log('执行componentDidMount')
    this.setState({ count: 1 })
  }
  onAddClick() {
    this.setState({ count: this.state.count + 1 })
  }
  onReduceClick() {
    this.setState({ count: this.state.count - 1 })
  }
  render () {
    const text = this.state.count % 2 === 0 ? '偶数' : '奇数'
    return (
      <View className="container">
        <View className="conut">
          <Text>count: {this.state.count}</Text>
        </View>
        <View>
          <Text className="text">{text}</Text>
        </View>
        <Button onClick={this.onAddClick} className="btn">+1</Button>
        <Button onClick={this.onReduceClick} className="btn">-1</Button>
      </View>
    )
  }
}
登入後複製

如果使用過 Taro 或 Remax 等框架,對上述程式碼應該有似曾相識的感覺,上述程式碼正式模仿這類框架的 React DSL 寫法。如果想迫切看到實現這個需求的效果,可點選此項目原始碼進行取得原始碼,然後根據提示運行項目,即可觀察到如下效果:

React如何建立小程式?兩種實現方案分享

到這裡,就清楚了知道這個專案的需求以及最終實現結果是什麼,接下來便是重點闡述從需求點到結果這個過程的具體實現。

實作方案

建構小程式框架產物

開發過小程式的同學都知道,小程式框架包含主體和頁面,其中主體是由三個檔案生組成的,且必須放在根目錄,這三個檔案分別是: app.js (必需,小程式邏輯),app.json(必需,小程式公共配置),app.wxss(非必須,小程式公共樣式表)。所以要將 React 程式碼建構成小程式碼,首先需要先生成app.jsapp.json檔。因為這次轉換未涉及到app.js文件,所以app.js內容可以直接寫死 App({})取代。 app.json是設定文件,可以直接在React 工程新增一個app.config.js用來填入設定內容,即React 程式碼工程目錄如下:

├── src
│   ├── app.config.js          // 小程序配置文件,用来生成app.json内容      
│   └── pages
│       └── index
│           ├── index.css
│           └── index.jsx      // React代码,即上述计数器代码
└── tsconfig.json
登入後複製

app.config.js內容就是小程式全域設定內容,如下:

module.exports = {
  pages: [&#39;pages/index/index&#39;],
  window: {
    navigationBarTitleText: &#39;react-wxapp&#39;,
    navigationBarBackgroundColor: &#39;#282c34&#39;
  }
};
登入後複製

有了這個設定文件,就可以透過以下方式產生app.jsapp.json檔案。

/*outputDir为小程序代码生成目录*/
fs.writeFileSync(path.join(outputDir, &#39;./app.js&#39;), `App({})`)
fs.writeFileSync(path.join(outputDir, &#39;./app.json&#39;), JSON.stringify(config, undefined, 2)) // config即为app.config.js文件内容
登入後複製

小程式頁面則是由四種類型檔案構成,分別是js(必需,頁面邏輯)、wxml(必需,頁面結是構) 、json(非必要、頁面配置)、wxss(非必要、頁面樣式表)。而React程式碼轉小程序,主要是考慮如何將React程式碼轉換程序對應的jswxml類型文件,後文會詳細闡述。

React運行到小程式平台方案分析

實現React程式碼運行到小程式平台上主要有兩種方式,一種是編譯時實現,一種是運行時實現,如果你已經查看的本專案專案原始碼,就可以發現原始碼裡也體現了這兩種方式(編譯時實作目錄:packages/compile-core;執行階段實作目錄:packages/runtime-core)。

編譯時方式主要透過靜態編譯將 JSX 轉換成小程式對應的 template 來實現渲染,類似 Taro1.0 和 2.0,此方式效能接近原生小程序,但是語法卻有很大的限制。運行時實作是透過react-reconciler重新在小程式平台定義一個React 渲染器,使得React 程式碼可以真正運行到小程式裡,類似Taro3.0、Remax 等,因此這種方式無語法限制,但是性能會比較差。本專案原始碼正是參考Taro、Remax 這類框架原始碼並簡化許多細節進行實現的,因此這個專案原始碼只是適合來學習的,並不能投入實際業務進行使用。

接下来将分别讲述如何通过编译时和运行时这两种方式来实现 React 运行到小程序平台。

编译时实现

在讲述具体实现流程之前,首先需要了解下编译时实现这个名词的概念,首先这里的编译并非传统的高大上“编译”,传统意义上的编译一般将高级语言往低级语言进行编译,但这里只是将同等水平语言转换,即将javascript代码字符串编译成另一种javascript代码字符串,因此这里的编译更类似于“转译”。其次,虽然这里称编译时实现,并非所有实现过程都是编译的,还是需要少部分实现需要运行时配合,因此这种方式称为重编译轻运行方式更为合适。同样的,运行时实现也含有少量编译时实现,亦可称为重运行轻编译方式。

为了方便实现将javascript代码字符串编译成另一种javascript代码字符串,这里直接采用Babel工具,由于篇幅问题,这里就不详细讲述Babel用法了,如果对Babel不熟的话,可以看看这篇文章简单了解下(没错,就是给自己打广告)。接下来我们来分析编译时实现步骤有哪些:

1. JSX转换成对应小程序的模板

React是通过JSX来渲染视图的,而小程序则通过wxml来渲染视图,要将 React 运行到小程序上,其重点就是要如何实现JSX转换成对应的小程序的wxml,其转换规则就是将JSX使用语法转换成小程序相同功能的语法,例如:

  • 标签元素转换:ViewTextButton等标签直接映射为小程序基础组件本身(改为小写)

  • 样式类名转换:className修改为class

    <View className="xxx" />  ==>  <View class="xxx" />
    登入後複製
  • 事件转换:如onClick修改为bindtap

    <View onClick=xxx />  ==>  <View bindtap =xxx />
    登入後複製
  • 循环转换:map语法修改为wx:for

    list.map(i => <Text>{i}</Text>) => <Text wx:for="{{list}}">{{item}}</Text>
    登入後複製

语法转换远不止上面这些类型,如果要保证开发者可以使用各种JSX语法开发小程序,就需要尽可能穷举出所有语法转换规则,否则很可能开发者用了一个写法就不支持转换。而事实是,有些写法(比如动态生成JSX片段等等)是根本无法支持转换,这也是前文为什么说编译时实现方案的缺点是语法有限制,开发者不能随意编码,需要受限于框架本身开发规则。

由于上述需要转换JSX代码语法相对简单,只需要涉及几种简单语法规则转换,这里直接贴出转换后的wxml结果如下,对应的实现代码位于:packages/compile-core/transform/parseTemplate.ts

<view class="container">
  <view class="conut"><Text>count: {{count}}</Text></view>
  <view>
    <text class="text">{{text}}</text>
  </view>
  <button bindtap="onAddClick" class="btn">+1</button>
  <button bindtap="onReduceClick" class="btn">-1</button>
</view>
登入後複製

2. 运行时适配

如前文所说,虽然这个方案称为编译时实现,但是要将React代码在小程序平台驱动运行起来,还需要在运行时做下适配处理。适配处理主要在小程序js逻辑实现,内容主要有三块:数据渲染、事件处理、生命周期映射。

小程序js逻辑是通过一个object参数配置声明周期、事件等来进行注册,并通过setData方法触发视图渲染:

Component({
  data: {},
  onReady () { this.setData(..) },
  handleClick () {}
})
登入後複製

而计数器React代码是通过class声明一个组件逻辑,类似:

class CustomComponent extends Component {
  state = { }
  componentDidMount() { this.setState(..)  }
  handleClick () { }
}
登入後複製

从上面两段代码可以看出,小程序是通过object声明逻辑,React 则是通过class进行声明。除此之外,小程序是通过setData触发视图(wxml)渲染,React 则是通过 setState 触发视图(render方法)渲染。所以要使得 React 逻辑可以运行到小程序平台,可以加入一个运行时垫片,将两者逻辑写法通过垫片对应起来。再介绍运行时垫片具体实现前,还需要对上述 React 计数器代码进行简单的转换处理,处理完的代码如下:

import React, { Component } from "../../npm/app.js";  // 1.app.js为垫片实现文件
export default class Index extends Component {
  static $$events = ["onAddClick", "onReduceClick"];  // 2.收集JSX事件名称
  constructor() {
    super();
    this.state = {
      count: 0
    };
    this.onAddClick = this.onAddClick.bind(this);
    this.onReduceClick = this.onReduceClick.bind(this);
  }
  componentDidMount() {
    console.log(&#39;执行componentDidMount&#39;);
    this.setState({
      count: 1
    });
  }
  onAddClick() {
    this.setState({
      count: this.state.count + 1
    });
  }
  onReduceClick() {
    this.setState({
      count: this.state.count - 1
    });
  }
  createData() {                                      // 3.render函数改为createData,删除
    this.__state = arguments[0];                      // 原本的JSX代码,返回更新后的state
                                                      // 提供给小程序进行setData
    const text = this.state.count % 2 === 0 ? &#39;偶数&#39; : &#39;奇数&#39;;
    Object.assign(this.__state, {
      text: text
    });
    return this.__state;
  }

}    
Page(require(&#39;../../npm/app.js&#39;).createPage(Index))。 // 4.使用运行时垫片提供的createPage
                                                      // 方法进行初始化
                                                      // 方法进行初始化复制代码
登入後複製

如上代码,需要处理的地方有4处:

  • Component进行重写,重写逻辑在运行时垫片文件内实现,即app.js,实现具体逻辑后文会贴出。

  • 将原本JSX的点击事件对应的回调方法名称进行收集,以便在运行时垫片在小程序平台进行事件注册。

  • 因为原本render方法内JSX片段转换为wxml了,所以这里render方法可将JSX片段进行删除。另外因为React每次执行setState都会触发render方法,而render方法内会接受到最新的state数据来更新视图,因此这里产生的最新state正是需要提供给小程序的setData方法,从而触发小程序的数据渲染,为此将render名称重命名为createData(生产小程序的data数据),同时改写内部逻辑,将产生的最新state进行返回。

  • 使用运行时垫片提供的createPage方法进行初始化(createPage方法实现具体逻辑后文会贴出),同时通过小程序平台提供的Page方法进行注册,从这里可得知createPage方法返回的数据肯定是一个object类型。

运行时垫片(app.js)实现逻辑如下:

export class Component {                             // 重写Component的实现逻辑
  constructor() {
    this.state = {}
  }
  setState(state) {                                  // setState最终触发小程序的setData
    update(this.$scope.$component, state)
  }
  _init(scope) {
    this.$scope = scope
  }
}
function update($component, state = {}) {
  $component.state = Object.assign($component.state, state)
  let data = $component.createData(state)            // 执行createData获取最新的state
  data[&#39;$leoCompReady&#39;] = true
  $component.state = data
  $component.$scope.setData(data)                    // 将state传递给setData进行更新
}
export function createPage(ComponentClass) {         // createPage实现逻辑
  const componentInstance = new ComponentClass()     // 实例化传入进来React的Class组件
  const initData = componentInstance.state     
  const option = {                                   // 声明一个小程序逻辑的对象字面量
    data: initData,
    onLoad() {
      this.$component = new ComponentClass()
      this.$component._init(this)
      update(this.$component, this.$component.state)
    },
    onReady() {
      if (typeof this.$component.componentDidMount === &#39;function&#39;) {
        this.$component.componentDidMount()           // 生命逻辑映射
      }
    }
  }
  const events = ComponentClass[&#39;$$events&#39;]          // 获取React组件内所有事件回调方法名称
  if (events) {
    events.forEach(eventHandlerName => {             
      if (option[eventHandlerName]) return
      option[eventHandlerName] = function () {
        this.$component[eventHandlerName].call(this.$component)
      }
    })
  }
  return option
}
登入後複製

上文提到了重写Component类和createPage方法具体实现逻辑如上代码所示。

Component内声明的state会执行一个update方法,update方法里主要是将 React 产生的新state和旧state进行合并,然后通过上文说的createData方法获取到合并后的最新state,最新的state再传递给小程序进行setData,从而实现小程序数据渲染。

createPage方法逻辑首先是将 React 组件实例化,然后构建出一个小程序逻辑的对应字面量,并将 React 组件实例相关方法和这个小程序逻辑对象字面量进行绑定:其次进行生命周期绑定:在小程序onReady周期里出发 React 组件对应的componentDidMount生命周期;最好进行事件绑定:通过上文提到的回调事件名,取出React 组件实例内的对应的事件,并将这些事件注册到小程序逻辑的对应字面量内,这样就完成小程序平台事件绑定。最后将这个对象字面量返回,供前文所说的Page方法进行注册。

到此,就可以实现 React 代码运行到小程序平台了,可以在项目源码里执行 npm run build:compile 看看效果。编译时实现方案主要是通过静态编译JSX代码和运行时垫片结合,完成 React 代码运行到小程序平台,这种方案基本无性能上的损耗,且可以在运行时垫片做一些优化处理(比如去除不必要的渲染数据,减少setData数据量),因此其性能与使用小程序原生语法开发相近甚至某些场景会更优。然而这种方案的缺点就是语法限制问题(上文已经提过了),使得开发并不友好,因此也就有了运行时实现方案的诞生。

运行时实现

从上文可以看出,编译时实现之所以有语法限制,主要因为其不是让 React 真正运行到小程序平台,而运行时实现方案则可以,其原理是在小程序平台实现一个 React 自定义渲染器,用来渲染 React 代码。这里我们以 remax 框架实现方式来进行讲解,本项目源码中的运行时实现也正是参照 remax 框架实现的。

如果使用过 React 开发过 Web,入口文件有一段类似这样的代码:

import React from &#39;react&#39;
import ReactDom from &#39;react-dom&#39;
import App from &#39;./App&#39;

ReactDom.render(
  App,
  document.getElementById(&#39;root&#39;)
)
登入後複製

可以看出渲染 Web 页面需要引用一个叫 react-dom 模块,那这个模块作用是什么?react-dom是 Web 平台的渲染器,主要负责将 React 执行后的Vitrual DOM数据渲染到 Web 平台。同样的,React 要渲染到 Native,也有一个针对 Native 平台的渲染器:React Native
React实现多平台方式,是在每个平台实现一个React渲染器,如下图所示。

React如何建立小程式?兩種實現方案分享

而如果要将 React 运行到小程序平台,只需要开发一个小程序自定义渲染器即可。React 官方提供了一个react-reconciler 包专门来实现自定义渲染器,官方提供了一个简单demo重写了react-dom

使用react-reconciler实现渲染器主要有两步,第一步:实现渲染函数(render方法),类似ReactDOM.render方法:

import ReactReconciler from &#39;react-reconciler&#39;
import hostConfig from &#39;./hostConfig&#39;      // 宿主配置

// 创建Reconciler实例, 并将HostConfig传递给Reconciler
const ReactReconcilerInst = ReactReconciler(hostConfig)

/**
 * 提供一个render方法,类似ReactDom.render方法
 * 与ReactDOM一样,接收三个参数
 * render(<MyComponent />, container, () => console.log(&#39;rendered&#39;))
 */
export function render(element, container, callback) {
  // 创建根容器
  if (!container._rootContainer) {
    container._rootContainer = ReactReconcilerInst.createContainer(container, false);
  }
  // 更新根容器
  return ReactReconcilerInst.updateContainer(element, container._rootContainer, null, callback);
}
登入後複製

第二步,如上图引用的import hostConfig from &#39;./hostConfig&#39; ,需要通过react-reconciler实现宿主配置(HostConfig),HostConfig是宿主环境提供一系列适配器方案和配置项,定义了如何创建节点实例、构建节点树、提交和更新等操作,完整列表可以点击查看。值得注意的是在小程序平台未提供DOM API操作,只能通过setData将数据传递给视图层。因此Remax重新定义了一个VNode类型的节点,让 React 在reconciliation过程中不是直接去改变DOM,而先更新VNodehostConfig文件内容大致如下:

interface VNode {
  id: number;              // 节点 id,这是一个自增的唯一 id,用于标识节点。
  container: Container;    // 类似 ReactDOM.render(<App />, document.getElementById(&#39;root&#39;) 中的第二个参数
  children: VNode[];       // 子节点。
  type: string | symbol;   // 节点的类型,也就是小程序中的基础组件,如:view、text等等。
  props?: any;             // 节点的属性。
  parent: VNode | null;    // 父节点
  text?: string;           // 文本节点上的文字
  appendChild(node: VNode): void;
  removeChild(node: VNode): void;
  insertBefore(newNode: VNode, referenceNode: VNode): void;
  ...
}

// 实现宿主配置
const hostConfig = {

  ...
  // reconciler提交后执行,触发容器更新数据(实际会触发小程序的setData)
  resetAfterCommit: (container) => {
    container.applyUpdate();
  },
  // 创建宿主组件实例,初始化VNode节点
  createInstance(type, newProps, container) {
    const id = generate();
    const node = new VNode({ ... });
    return node;
  },
  // 插入节点
  appendChild(parent, child) {
    parent.appendChild(child);
  },
  // 
  insertBefore(parent, child, beforeChild) {
    parent.insertBefore(child, beforeChild);
  },
  // 移除节点
  removeChild(parent, child) {
    parent.removeChild(child);
  }
  
  ...
  
};
登入後複製

除了上面的配置内容,还需要提供一个容器用来将VNode数据格式化为JSON数据,供小程序setData传递给视图层,这个容器类实现如下:

class Container {
  constructor(context) {
    this.root = new VNode({..});   // 根节点
  }

  toJson(nodes ,data) {            // 将VNode数据格式化JSON
    const json = data || []
    nodes.forEach(node => {
      const nodeData = {
        type: node.type,
        props: node.props || {},
        text: node.text,
        id: node.id,
        children: []
      }
      if (node.children) {
        this.toJson(node.children, nodeData.children)
      }
      json.push(nodeData)
    })
    return json
  }
  applyUpdate() {                 // 供HostConfig配置的resetAfterCommit方法执行
    const root = this.toJson([this.root])[0]
    console.log(root)
    this.context.setData({ root});
  }
  ...
}
登入後複製

紧接着,我们封装一个createPageConfig方法,用来执行渲染,其中Page参数为 React 组件,即上文计数器的组件。

import * as React from &#39;react&#39;;
import Container from &#39;./container&#39;; // 上文定义的Container
import render from &#39;./render&#39;;       // 上文定义的render方法

export default function createPageConfig(component) {  // component为React组件
  const config = {  // 小程序逻辑对象字面量,供Page方法注册
    data: {
      root: {
        children: [],
      }
    },
    onLoad() {
      this.container = new Container(this, &#39;root&#39;);
      const pageElement = React.createElement(component, {
        page: this,
      });

      this.element = render(pageElement, this.container);
    }
  };

  return config;
}
登入後複製

到这里,基本已经实现完小程序渲染器了,为了使代码跑起来,还需要通过静态编译改造下 React 计数器组件,其实就是在末尾插入一句代码:

import React, { Component } from &#39;react&#39;;
export default class Index extends Component {
  constructor() {
    super();
    this.state = {
      count: 0
    };
    this.onAddClick = this.onAddClick.bind(this);
    this.onReduceClick = this.onReduceClick.bind(this);
  }
  ...

} 
// app.js封装了上述createPage方法
Page(require(&#39;../../npm/app.js&#39;).createPage(Index))
登入後複製

通过这样,就可以使得React代码在小程序真正运行起来了,但是这里我们还有个流程没介绍,上述Container类的applyUpdate方法中生成的页面JSON数据要如何更新到视图?首先我们先来看下这个JSON数据长什么样子:

// 篇幅问题,这里只贴部分数据
{
	"type": "root",
	"props": {},
	"id": 0,
	"children": [{
		"type": "view",
		"props": {
			"class": "container"
		},
		"id": 12,
		"children": [{
			"type": "view",
			"props": {
				"class": "conut"
			},
			"id": 4,
			"children": [{
				"type": "text",
				"props": {},
				"id": 3,
				"children": [{
					"type": "plain-text",
					"props": {},
					"text": "count: ",
					"id": 1,
					"children": []
				}, {
					"type": "plain-text",
					"props": {},
					"text": "1",
					"id": 2,
					"children": []
				}]
			}]
		}
 	...
 	...
 		
	}]
}
登入後複製

可以看出JSON数据,其实是一棵类似Tree UI的数据,要将这些数据渲染出页面,可以使用小程序提供的Temlate进行渲染,由于小程序模板递归嵌套会有问题(微信小程序平台限制),因此需要提供多个同样组件类型的模板进行递归渲染,代码如下:

<template is="TPL" data="{{root: root}}" />  <!-- root为上述的JSON数据 -->


<template name="TPL">
 <block wx:for="{{root.children}}" wx:key="id">
  <template is="TPL_1_CONTAINER" data="{{i: item, a: &#39;&#39;}}" />
 </block>
</template>

    
<template name="TPL_1_view">
  <view
    style="{{i.props.style}}"
    class="{{i.props.class}}"
    bindtap="{{i.props.bindtap}}"
  >
    <block wx:for="{{i.children}}" wx:key="id">
      <template is="{{&#39;TPL_&#39; + (tid + 1) + &#39;_CONTAINER&#39;}}" data="{{i: item, a: a, tid: tid + 1 }}" />
    </block>
  </view>
</template>
  
<template name="TPL_2_view">
  <view
    style="{{i.props.style}}"
    class="{{i.props.class}}"
    bindtap="{{i.props.bindtap}}"
  >
    <block wx:for="{{i.children}}" wx:key="id">
      <template is="{{&#39;TPL_&#39; + (tid + 1) + &#39;_CONTAINER&#39;}}" data="{{i: item, a: a, tid: tid + 1 }}" />
    </block>
  </view>
</template>
  
<template name="TPL_3_view">
  <view
    style="{{i.props.style}}"
    class="{{i.props.class}}"
    bindtap="{{i.props.bindtap}}"
  >
    <block wx:for="{{i.children}}" wx:key="id">
      <template is="{{&#39;TPL_&#39; + (tid + 1) + &#39;_CONTAINER&#39;}}" data="{{i: item, a: a, tid: tid + 1 }}" />
    </block>
  </view>
</template>
 
...
...
登入後複製

至此,就可以真正实现 React 代码运行到小程序了,可以在项目源码里执行npm run build:runtime看看效果。运行时方案优点是无语法限制,(不信的话,可以在本项目里随便写各种动态写法试试哦),而缺点时性能比较差,主要原因是因为其setData数据量比较大(上文已经贴出的JSON数据,妥妥的比编译时方案大),因此性能就比编译时方案差。当然了,业界针对运行时方案也有做大量的性能优化,比如局部更新、虚拟列表等,由于篇幅问题,这里就不一一讲解(代码中也没有实现)。

总结

本文以最简实现方式讲述了 React 构建小程序两种实现方案,这两种方案优缺点分明,都有各自的优势,对于追求性能好场的场景,编译时方案更为合适。对于着重开发体验且对性能要求不高的场景,运行时方案为首选。如果想了解更多源码实现,可以去看下 TaroRemax 官方源码,欢迎互相讨论。

项目地址

https://github.com/canfoo/react-wxapp

【相关学习推荐:小程序开发教程

以上是React如何建立小程式?兩種實現方案分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何利用React和WebSocket建立即時聊天應用 如何利用React和WebSocket建立即時聊天應用 Sep 26, 2023 pm 07:46 PM

如何利用React和WebSocket建立即時聊天應用程式引言:隨著網路的快速發展,即時通訊越來越受到人們的關注。即時聊天應用程式已成為現代社交和工作生活中不可或缺的一部分。本文將介紹如何利用React和WebSocket建立一個簡單的即時聊天應用,並提供具體的程式碼範例。一、技術準備在開始建立即時聊天應用之前,我們需要準備以下技術和工具:React:一個用於構建

React前後端分離指南:如何實作前後端的解耦與獨立部署 React前後端分離指南:如何實作前後端的解耦與獨立部署 Sep 28, 2023 am 10:48 AM

React前後端分離指南:如何實現前後端的解耦和獨立部署,需要具體程式碼範例在當今的Web開發環境中,前後端分離已經成為一種趨勢。透過將前端和後端程式碼分開,可以讓開發工作更加靈活、高效,並且方便進行團隊協作。本文將介紹如何使用React實現前後端分離,從而實現解耦和獨立部署的目標。首先,我們要先理解什麼是前後端分離。傳統的Web開發模式中,前端和後端是耦合在

如何利用React和Flask建構簡單易用的網路應用 如何利用React和Flask建構簡單易用的網路應用 Sep 27, 2023 am 11:09 AM

如何利用React和Flask建構簡單易用的網路應用引言:隨著網路的發展,網路應用的需求也越來越多樣化和複雜化。為了滿足使用者對於易用性和效能的要求,使用現代化的技術堆疊來建立網路應用變得越來越重要。 React和Flask是兩個在前端和後端開發中非常受歡迎的框架,它們可以很好的結合在一起,用來建立簡單易用的網路應用。本文將詳細介紹如何利用React和Flask

如何利用React和RabbitMQ建立可靠的訊息應用 如何利用React和RabbitMQ建立可靠的訊息應用 Sep 28, 2023 pm 08:24 PM

如何利用React和RabbitMQ建立可靠的訊息傳遞應用程式引言:現代化的應用程式需要支援可靠的訊息傳遞,以實現即時更新和資料同步等功能。 React是一種流行的JavaScript庫,用於建立使用者介面,而RabbitMQ是一種可靠的訊息傳遞中間件。本文將介紹如何結合React和RabbitMQ建立可靠的訊息傳遞應用,並提供具體的程式碼範例。 RabbitMQ概述:

React程式碼偵錯指南:如何快速定位與解決前端bug React程式碼偵錯指南:如何快速定位與解決前端bug Sep 26, 2023 pm 02:25 PM

React程式碼偵錯指南:如何快速定位並解決前端bug引言:在開發React應用程式時,經常會遇到各種各樣的bug,這些bug可能會使應用程式崩潰或導致不正確的行為。因此,掌握調試技巧是每個React開發者必備的能力。本文將介紹一些定位和解決前端bug的實用技巧,並提供具體的程式碼範例,幫助讀者快速定位和解決React應用程式中的bug。一、調試工具的選擇:在Re

React Router使用指南:如何實現前端路由控制 React Router使用指南:如何實現前端路由控制 Sep 29, 2023 pm 05:45 PM

ReactRouter使用指南:如何實現前端路由控制隨著單頁應用的流行,前端路由成為了一個不可忽視的重要部分。 ReactRouter作為React生態系統中最受歡迎的路由庫,提供了豐富的功能和易用的API,使得前端路由的實作變得非常簡單和靈活。本文將介紹ReactRouter的使用方法,並提供一些具體的程式碼範例。安裝ReactRouter首先,我們需要

如何利用React和Google BigQuery建立快速的資料分析應用 如何利用React和Google BigQuery建立快速的資料分析應用 Sep 26, 2023 pm 06:12 PM

如何利用React和GoogleBigQuery建立快速的資料分析應用引言:在當今資訊爆炸的時代,資料分析已經成為了各產業中不可或缺的環節。而其中,建構快速、有效率的資料分析應用則成為了許多企業和個人追求的目標。本文將介紹如何利用React和GoogleBigQuery結合來建立快速的資料分析應用,並提供詳細的程式碼範例。一、概述React是用來構建

如何利用React和Docker打包部署前端應用 如何利用React和Docker打包部署前端應用 Sep 26, 2023 pm 03:14 PM

如何利用React和Docker打包和部署前端應用前端應用的打包和部署是專案開發中非常重要的一環。隨著現代前端框架的快速發展,React成為了許多前端開發人員的首選。而Docker作為一種容器化解決方案,可以大幅簡化應用的部署過程。本文將介紹如何利用React和Docker打包和部署前端應用,並提供具體的程式碼範例。一、準備工作在開始前,我們需要先安裝

See all articles