vue3 の開発では、reactive はレスポンシブ データを実装するメソッドを提供します。これは日常の開発で頻繁に使用される API です。この記事では、著者はその内部動作メカニズムを探ります。初心者なので、稚拙な文章ですがご容赦ください。
デバッグ バージョンは 3.2.45です
-
リアクティブとは何ですか?
リアクティブとは、提供される実装応答です。 Vue3 のリアクティブ データ メソッド。
Vue2 では、リアクティブ データは defineProperty.
Vue3 では、リアクティブ データは ES6 の Proxy ## を通じて実装されます。
- #リアクティブ アテンション ポイントを達成するにはreactive パラメータは
object(json/arr)
if 他のオブジェクトである必要がありますデフォルトでは、オブジェクトが変更されてもインターフェースは自動的に更新されません。更新したい場合は、値を再割り当てできます。 [関連する推奨事項:
<script setup> import {reactive} from 'vue' const data = reactive({ //定义对象 name:'测试', age:10 }) const num = reactive(1)//定义基本数据类型 console.log(data)//便于定位到调试位置 </script> <template> <div> <h1 id="nbsp-data-name-nbsp">{{ data.name }}</h1> </div> </template> <style scoped></style>ブレークポイントの設定
##デバッグの開始
次にデバッグを開始できます。ブレークポイントを設定した後、ページを更新してデバッグ インターフェイスに入ります。
複雑なデータ型
最初に単純な基本データ型をデバッグしましょう
1./*1.初始进来函数,判断目标对象target是否为只读对象,如果是直接返回*/ function reactive(target) { // if trying to observe a readonly proxy, return the readonly version. if (isReadonly(target)) { return target; } //创建一个reactive对象,五个参数后续会讲解 return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap); } /*2.判断是来判断target是否为只读。*/ function isReadonly(value) { return !!(value && value["__v_isReadonly" /* ReactiveFlags.IS_READONLY */]); } /*3.创建一个reactive对象*/ /*createReactiveObject接收五个参数: target被代理的对象, isReadonl是不是只读的, baseHandlers proxy的捕获器, collectionHandlers针对集合的proxy捕获器, proxyMap一个用于缓存proxy的`WeakMap`对象*/ function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) { //如果target不是对象则提示并返回 /*这里会跳转到如下方法 判断是否原始值是否为object类型 const isObject = (val) => val !== null && typeof val === 'object'; */ if (!isObject(target)) { if ((process.env.NODE_ENV !== 'production')) { console.warn(`value cannot be made reactive: ${String(target)}`); } return target; } // 如果target已经是proxy是代理对象则直接返回. if (target["__v_raw" /* ReactiveFlags.RAW */] && !(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) { return target; } // 从proxyMap中获取缓存的proxy对象,如果存在的话,直接返回proxyMap中对应的proxy。否则创建proxy。 const existingProxy = proxyMap.get(target); if (existingProxy) { return existingProxy; } // 并不是任何对象都可以被proxy所代理。这里会通过getTargetType方法来进行判断。 const targetType = getTargetType(target); //当类型值判断出是不能代理的类型则直接返回 if (targetType === 0 /* TargetType.INVALID */) { return target; } //通过使用Proxy函数劫持target对象,返回的结果即为响应式对象了。这里的处理函数会根据target对象不同而不同(这两个函数都是参数传入的): //Object或者Array的处理函数是collectionHandlers; //Map,Set,WeakMap,WeakSet的处理函数是baseHandlers; const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers); proxyMap.set(target, proxy); return proxy; }

メソッド呼び出しプロセス <pre class='brush:php;toolbar:false;'>//1.进入判断如果value有__v_skip属性且为true或对象是可拓展则返回0,否则走类型判断函数
function getTargetType(value) {
//Object.isExtensible() 方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
return value["__v_skip" /* ReactiveFlags.SKIP */] || !Object.isExtensible(value)
? 0 /* TargetType.INVALID */
: targetTypeMap(toRawType(value));
}
//2.这里通过Object.prototype.toString.call(obj)来判断数据类型
const toRawType = (value) => {
// extract "RawType" from strings like "[object RawType]"
return toTypeString(value).slice(8, -1);
};
const toTypeString = (value) => objectToString.call(value);
//3.这里rawType是为&#39;Object&#39;所以会返回1
function targetTypeMap(rawType) {
switch (rawType) {
case &#39;Object&#39;:
case &#39;Array&#39;:
return 1 /* TargetType.COMMON */;
case &#39;Map&#39;:
case &#39;Set&#39;:
case &#39;WeakMap&#39;:
case &#39;WeakSet&#39;:
return 2 /* TargetType.COLLECTION */;
default:
return 0 /* TargetType.INVALID */;//返回0说明除前面的类型外其他都不能被代理,如Date,RegExp,Promise等
}
}</pre>
メソッド内const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers :baseHandlers);
このステートメントの 2 番目のパラメーターは、ターゲットが Map タイプであるか Set タイプであるかを決定します。したがって、依存関係の収集には別のハンドラーが使用されます。 デバッグ ファイル
で、reactive
関数 ##2 つのパラメーターから ReactiveObject# を作成します関数呼び出し mutableHandlers
および mutableCollectionHandlers
は、mutableHandlers
const mutableHandlers = { get,// 获取值的拦截,访问对象时会触发 set,// 更新值的拦截,设置对象属性会触发 deleteProperty,// 删除拦截,删除对象属性会触发 has,// 绑定访问对象时会拦截,in操作符会触发 ownKeys// 获取属性key列表 }; function deleteProperty(target, key) { // key是否是target自身的属性 const hadKey = hasOwn(target, key); // 旧值 const oldValue = target[key]; // 调用Reflect.deleteProperty从target上删除属性 const result = Reflect.deleteProperty(target, key); // 如果删除成功并且target自身有key,则触发依赖 if (result && hadKey) { trigger(target, "delete" /* TriggerOpTypes.DELETE */, key, undefined, oldValue); } return result; } // function has(target, key) { //检查目标对象是否存在此属性。 const result = Reflect.has(target, key); // key不是symbol类型或不是symbol的内置属性,进行依赖收集 if (!isSymbol(key) || !builtInSymbols.has(key)) { track(target, "has" /* TrackOpTypes.HAS */, key); } return result; } /*ownKeys可以拦截以下操作: 1.Object.keys() 2.Object.getOwnPropertyNames() 3.Object.getOwnPropertySymbols() 4.Reflect.ownKeys()操作*/ function ownKeys(target) { track(target, "iterate" /* TrackOpTypes.ITERATE */, isArray(target) ? 'length' : ITERATE_KEY); return Reflect.ownKeys(target); }
get# の実装のクエリを開始します。 ##メソッドの実装
const get = /*#__PURE__*/ createGetter();
/*传递两个参数默认都为false
isReadonly是否为只读
shallow是否转换为浅层响应,即Reactive---> shallowReactive,shallowReactive监听了第一层属性的值,一旦发生改变,则更新视图;其他层,虽然值发生了改变,但是视图不会进行更新
*/
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
//1.是否已被reactive相关api处理过;
if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
return !isReadonly;
}
//2.是否被readonly相关api处理过
else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
return isReadonly;
}
else if (key === "__v_isShallow" /* ReactiveFlags.IS_SHALLOW */) {
return shallow;
}
//3.检测__v_raw属性
else if (key === "__v_raw" /* ReactiveFlags.RAW */ &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap).get(target)) {
return target;
}
//4.如果target是数组,且命中了一些属性,则执行函数方法
const targetIsArray = isArray(target);
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
//5.Reflect获取值
const res = Reflect.get(target, key, receiver);
//6.判断是否为特殊的属性值
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res;
}
if (!isReadonly) {
track(target, "get" /* TrackOpTypes.GET */, key);
}
if (shallow) {
return res;
}
//7.判断是否为ref对象
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value;
}
//8.判断是否为对象
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
Detection
__v_isReactive
属性が true の場合、- target
- はすでにリアクティブ オブジェクトであることを意味します。
属性と__v_isReadonly
__v_isShallow - 属性を順番に検出して、それが読み取り専用で浅い応答であるかどうかを判断します。対応パッケージ対象です。
属性を検出します。ここでは 3 項のネストが示されており、主に元のデータが__v_raw
read-only - か
であるかを判断します。浅い
が配列の場合、一部のメソッドを変更する必要があります (に応答し、対応する
Map でターゲット オブジェクトを探します。両方が true の場合、ターゲットはすでに応答オブジェクトであることを意味します。 target includes - 、
indexOf、## の場合) #lastIndexOf,
push
,pop
,shift
,unshift
,splice
) は特殊な処理を行います。そして、配列の各要素に対してコレクションの依存関係を実行し、Reflect を通じて配列関数の値を取得します。反映
値を取得します。 -
特別な属性値であるかどうかを判断します (
__proto__symbol
、 、 - __v_isRef
、
配列でない場合は、自動的に解凍されます。__isVue#) # #、後続の処理を行わずに、前に取得した
resを直接返す場合;
それが
refオブジェクトの場合、
target -
の場合、ディープ レスポンシブ処理を実行します。ここから、res
が
Object Proxy - が応答オブジェクトを遅延的に作成することがわかります。対応する
key
例:がアクセスされた場合にのみ応答オブジェクトが作成され続けます。それ以外の場合は、応答オブジェクトを作成する必要はありません。 。
set
メソッド実装
data.name='2'
const set = /*#__PURE__*/ createSetter();
//shallow是否转换为浅层响应,默认为false
function createSetter(shallow = false) {
//1.传递四个参数
return function set(target, key, value, receiver) {
let oldValue = target[key];
//首先获取旧值,如果旧值是ref类型,且新值不是ref类型,则不允许修改
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false;
}
//2.根据传递的shallow参数,来执行之后的操作
if (!shallow) {
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue);
value = toRaw(value);
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
}
//3.检测key是不是target本身的属性
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
//利用Reflect.set()来修改值,返回一个Boolean值表明是否成功设置属性
//Reflect.set(设置属性的目标对象, 设置的属性的名称, 设置的值, 如果遇到 `setter`,`receiver`则为`setter`调用时的`this`值)
const result = Reflect.set(target, key, value, receiver);
// 如果目标是原始原型链中的某个元素,则不要触发
if (target === toRaw(receiver)) {
//如果不是target本身的属性那么说明执行的是'add'操作,增加属性
if (!hadKey) {
trigger(target, "add" /* TriggerOpTypes.ADD */, key, value);
}
//4.比较新旧值,是否触发依赖
else if (hasChanged(value, oldValue)) {
//5.触发依赖
trigger(target, "set" /* TriggerOpTypes.SET */, key, value, oldValue);
}
}
return result;
};
}
1 , コード
data.name='2'
を例にとると、4 つのパラメータは次のとおりです: target
target = {"name": "Test", "age": 10}(これは通常のオブジェクトです)
key: 変更する対応するキー、つまり、
key: "name"
value: 変更された値、つまり
value: "2"
receiver: ターゲット オブジェクトのプロキシ。つまり、
receiver=Proxy {"name": "test","age": 10}
2、shallow
为false的时候。
第一个判断:如果新值不是浅层响应式并且不是readonly,新旧值取其对应的原始值。
第二个判断:如果target不是数组并且旧值是ref类型,新值不是ref类型,直接修改oldValue.value为value
3.检测key
是不是target本身的属性。这里的hadKey
有两个方法,isArray
就不解释,就是判断是否为数组
isIntegerKey
:判断是不是数字型的字符串key值
//判断参数是否为string类型,是则返回true const isString = (val) => typeof val === 'string'; //如果参数是string类型并且不是'NaN',且排除-值(排除负数),然后将 key 转换成数字再隐式转换为字符串,与原 key 对比 const isIntegerKey = (key) => isString(key) && key !== 'NaN' && key[0] !== '-' && '' + parseInt(key, 10) === key;
4.比较新旧值,如果新旧值不同,则触发依赖进行更新
hasChanged
方法
//Object.is()方法判断两个值是否是相同的值。 const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
5.触发依赖,这里太过复杂,笔者也没搞懂,如果有兴趣的读者可自行去调试
<script setup> import { reactive } from "vue"; const data = reactive({ name: "测试", age: 10, }); data.name='1'//这里并未收集依赖,在处理完 createSetupContext 的上下文后,组件会停止依赖收集,并且开始执行 setup 函数。具体原因有兴趣的读者可以自行去了解 const testClick = ()=>{ data.name='test' } </script> <template> <div> <h1 id="nbsp-data-name-nbsp">{{ data.name }}</h1> <el-button @click="testClick">Click</el-button> </div> </template> <style scoped></style>
基本数据类型
const num = reactive(2)
这里比较简单,在createReactiveObject
函数方法里面:
if (!isObject(target)) { if ((process.env.NODE_ENV !== 'production')) { console.warn(`value cannot be made reactive: ${String(target)}`); } return target; }
因为判断类型不是对象,所以会在控制台打印出警告,并且直接返回原数据
proxy对象
<script> const data = reactive({ name: "测试", age: 10, }); const num = reactive(data)//定义一个已经是响应式对象 </script>
1.调试开始进来reactive
函数,然后会经过isReadonly
函数,这里跟前面不同的是,target是一个proxy对象,它已经被代理过有set
,get
等handler。所以在isReadonly
函数读取target
的时候,target
会进行get
函数的读取操作。
function reactive(target) { // if trying to observe a readonly proxy, return the readonly version. if (isReadonly(target)) { return target; } return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap); }
2.可以看到get
传入的参数有个key="__v_isReadonly"
,这里的isReadonly
返回是false,接下来进入createReactiveObject
函数
这里说明下,在本次调试中常见的vue里面定义的私有属性有:
-
__v_skip
:是否无效标识,用于跳过监听 -
__v_isReactive
:是否已被reactive相关api处理过 -
__v_isReadonly
:是否被readonly相关api处理过 -
__v_isShallow
:是否为浅层响应式对象 -
__v_raw
:当前代理对象的源对象,即target
3.在createReactiveObject
函数中,经过target["__v_isReactive"]
的时候会触发target
的get函数,这时候get
函数传入的参数中key='__v_raw'
if (target["__v_raw" /* ReactiveFlags.RAW */] && !(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) { return target; }
由上图可知我们检测target
即已定义过的proxy对象,被reactive
api处理过就会有__v_raw
私有属性,然后再进行receiver
的判断,判断target
是否为只读或浅层响应。如果都不是则从缓存proxy的WeakMap
对象中获取该元素。最后直接返回target
的原始数据(未被proxy代理过)。
最后回到之前的判断,由下图可知,target
的__v_raw
属性存在,isReadonly
为false,__v_isReactive
的值为true,可以说明reactive
函数需要处理的对象是一个被reactive
API处理过的对象,然后直接返回该对象的原始数据。
ref类型
经过ref
函数处理,其本质也是一个对象,所以使用reactive
函数处理ref
类型就跟处理复杂数据类型一样过程。对于ref
函数,如果大家有兴趣可以阅读这篇文章vue3——深入了解ref()。有些内容跟这里差不多,也有对此补充,如果觉得不错请各位帮忙点个赞
(开发中应该不会有这种嵌套行为吧,这里只是为了测试多样化)。
<script setup> import { reactive,ref } from "vue"; const data = reactive({ name: "测试", age: 10, }); const numRef = ref(1) const dataRef = ref({ name: "测试2", age: 20, }) const num = reactive(numRef) const dataReactive = reactive(dataRef) console.log('data',data) console.log('numRef',numRef) console.log('num',num) console.log('dataRef',dataRef) console.log('dataReactive',dataReactive) </script>
Map类型和Set类型
-
Map
类型是键值对的有序列表,而键和值都可以是任意类型。 -
Set
和Map
类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set
中,没有重复的key。
<script setup> import { reactive } from "vue"; const mapData = new Map(); mapData.set('name','张三') const setData = new Set([1,2,3,1,1]) console.log(mapData) console.log(setData) const mapReactive = reactive(mapData) console.log(mapReactive) </script>
由上图可知Map结构和Set结构使用typeof
判断是object
,所有流程前面会跟复杂数据类型一样,知道在createReactiveObject
函数的getTargetType()
函数开始不同。
在getTargetType
函数里面toRawType()
判断数据类型所用方法为Object.prototype.toString.call()
const targetType = getTargetType(target); function getTargetType(value) { return value["__v_skip" /* ReactiveFlags.SKIP */] || !Object.isExtensible(value) ? 0 /* TargetType.INVALID */ : targetTypeMap(toRawType(value)); } function targetTypeMap(rawType) {//rawType="Map",这里返回值为2 switch (rawType) { case 'Object': case 'Array': return 1 /* TargetType.COMMON */; case 'Map': case 'Set': case 'WeakMap': case 'WeakSet': return 2 /* TargetType.COLLECTION */; default: return 0 /* TargetType.INVALID */; } }
这时候targetType=2
,在createReactiveObject
的函数中const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
的三元表达式中可得知,这里的handler
为collectionHandlers
。
网上查找可在reactive
函数中return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
这条语句找到,当rawType=1
时handler
是用mutableHandlers
,rawType=1
时是用mutableCollectionHandlers
。
mutableCollectionHandlers
方法:
const mutableCollectionHandlers = { get: /*#__PURE__*/ createInstrumentationGetter(false, false) }; //解构createInstrumentations const [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations, shallowReadonlyInstrumentations] = /* #__PURE__*/ createInstrumentations(); //传入两个参数,是否为可读,是否为浅层响应 function createInstrumentationGetter(isReadonly, shallow) { const instrumentations = shallow ? isReadonly ? shallowReadonlyInstrumentations : shallowInstrumentations : isReadonly ? readonlyInstrumentations : mutableInstrumentations; return (target, key, receiver) => { if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) { return !isReadonly; } else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) { return isReadonly; } else if (key === "__v_raw" /* ReactiveFlags.RAW */) { return target; } return Reflect.get(hasOwn(instrumentations, key) && key in target ? instrumentations : target, key, receiver); }; }
//篇幅问题以及这方面笔者并未深入,所以就大概带过 function createInstrumentations() { //创建了四个对象,对象内部有很多方法,其他去掉了,完整可自行去调试查看 const mutableInstrumentations = { get(key) { return get$1(this, key); }, get size() { return size(this); }, has: has$1, add, set: set$1, delete: deleteEntry, clear, forEach: createForEach(false, false) }; ................. //通过createIterableMethod方法操作keys、values、entries、Symbol.iterator迭代器方法 const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]; iteratorMethods.forEach(method => { mutableInstrumentations[method] = createIterableMethod(method, false, false); readonlyInstrumentations[method] = createIterableMethod(method, true, false); shallowInstrumentations[method] = createIterableMethod(method, false, true); shallowReadonlyInstrumentations[method] = createIterableMethod(method, true, true); }); return [ mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations, shallowReadonlyInstrumentations ]; }
后续比较复杂,加上笔者技术力还不够,如果想继续深入的读者,可以阅读这篇文章:Vue3响应式原理
总结:关于reactive
的源码调试就到这了,这只是其中一小部分的源码,希望有兴趣的读者可以以此深入,输出文章,共同进步成长。最后,如果这篇文章对你有所收获,请点个赞,如果有写的不对的地方,请大佬们指出(* ̄︶ ̄)。
以上がvue3 の reactive() について詳しく話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

Vue.JSは中小企業から中規模のプロジェクトに適していますが、Reactは大規模で複雑なアプリケーションにより適しています。 1。VUE.JSのレスポンシブシステムは、依存関係追跡を介してDOMを自動的に更新し、データの変更を簡単に管理できるようにします。 2.反応は一方向のデータフローを採用し、データは親コンポーネントから子コンポーネントに流れ、明確なデータフローと簡単な抽出構造を提供します。

VUE.JSは、中小規模のプロジェクトや迅速な反復に適していますが、Reactは大規模で複雑なアプリケーションに適しています。 1)Vue.jsは使いやすく、チームが不十分な状況やプロジェクトスケールが小さい状況に適しています。 2)Reactにはより豊富なエコシステムがあり、高性能で複雑な機能的ニーズを持つプロジェクトに適しています。

VUEでタグのジャンプを実装する方法には、HTMLテンプレートでAタグを使用してHREF属性を指定する方法が含まれます。 VUEルーティングのルーターリンクコンポーネントを使用します。 JavaScriptでこれを使用します。$ router.push()メソッド。パラメーターはクエリパラメーターに渡すことができ、ルートは動的ジャンプのルーターオプションで構成されています。

VUEでコンポーネントジャンプを実装するための次の方法があります。Router-Linkと&lt; router-view&gt;を使用してください。ハイパーリンクジャンプを実行し、ターゲットパスとして属性を指定するコンポーネント。 &lt; router-view&gt;を使用してください現在ルーティングされているレンダリングされているコンポーネントを表示するコンポーネント。プログラマティックナビゲーションには、router.push()およびrouter.replace()メソッドを使用します。前者は歴史を保存し、後者は記録を残さずに現在のルートに取って代わります。

VUEにDIV要素をジャンプするには、VUEルーターを使用してルーターリンクコンポーネントを追加するには、2つの方法があります。 @clickイベントリスナーを追加して、これを呼び出します。$ router.push()メソッドをジャンプします。

VUEにデータを渡す主な方法は2つあります。PROPS:一方向データバインディング、親コンポーネントから子コンポーネントにデータを渡します。イベント:イベントとカスタムイベントを使用してコンポーネント間でデータを渡します。

Vue.jsは、ジャンプする3つの方法を提供します。ネイティブJavaScript API:Window.Location.hrefを使用してジャンプします。 Vueルーター:&lt; router-link&gt;を使用してくださいタグまたはこれ。$ router.push()ジャンプする方法。 Vuex:トリガールートジャンプを発送するか、突然変異をコミットします。

ルーターリンクコンポーネントを使用してクリック可能なリンクを作成するなど、VUEでページリダイレクトを設定する方法はいくつかあります。 router.push()メソッドを使用して、履歴スタックに新しいルートを手動で追加します。 router.replace()メソッドを使用して、現在のルートを置き換えます。 location.hrefを使用して直接新しいページにリダイレクトします。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

Safe Exam Browser
Safe Exam Browser は、オンライン試験を安全に受験するための安全なブラウザ環境です。このソフトウェアは、あらゆるコンピュータを安全なワークステーションに変えます。あらゆるユーティリティへのアクセスを制御し、学生が無許可のリソースを使用するのを防ぎます。

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

AtomエディタMac版ダウンロード
最も人気のあるオープンソースエディター

SublimeText3 英語版
推奨: Win バージョン、コードプロンプトをサポート!

メモ帳++7.3.1
使いやすく無料のコードエディター

ホットトピック



