Detele functional mini (#1204)

* feat: calendar组件去掉hook实现

* feat: demo去hook相关的依赖
This commit is contained in:
homi 2024-06-13 21:21:47 +08:00 committed by GitHub
parent 204eb27fe9
commit 448bfc323d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 236 additions and 1719 deletions

View File

@ -1,21 +1,38 @@
import dayjs from 'dayjs';
import equal from 'fast-deep-equal';
import Converter from './js-calendar-converter';
import { mountComponent } from '../../../../../src/_util/component';
const CollapseContainer = (props) => {
var _a, _b, _c;
const time = dayjs((_a = props.cell) === null || _a === void 0 ? void 0 : _a.time);
const vs = Converter.solar2lunar(time.get('year'), time.get('month') + 1, time.get('date'));
if (vs === -1) {
return {
cnday: '',
};
}
return {
cnday: vs.lunarFestival || vs.festival || vs.IDayCn,
festival: !!vs.festival || !!vs.lunarFestival,
unset: ((_b = props.cell) === null || _b === void 0 ? void 0 : _b.isSelectedBegin) || ((_c = props.cell) === null || _c === void 0 ? void 0 : _c.isSelectedEnd),
};
};
mountComponent(CollapseContainer, {
import { Component, getValueFromProps } from '../../../../../src/_util/simply';
Component({
cell: null,
}, {
updateData() {
const cell = getValueFromProps(this, 'cell');
const time = dayjs(cell === null || cell === void 0 ? void 0 : cell.time);
const vs = Converter.solar2lunar(time.get('year'), time.get('month') + 1, time.get('date'));
if (vs === -1) {
this.setData({
cnday: '',
});
return;
}
this.setData({
cnday: vs.lunarFestival || vs.festival || vs.IDayCn,
festival: !!vs.festival || !!vs.lunarFestival,
unset: (cell === null || cell === void 0 ? void 0 : cell.isSelectedBegin) || (cell === null || cell === void 0 ? void 0 : cell.isSelectedEnd),
});
},
}, {
cnday: '',
festival: '',
unset: '',
}, null, {
onInit() {
this.updateData();
},
didUpdate(prevProps) {
const cell = getValueFromProps(this, 'cell');
if (!equal(prevProps.cell, cell)) {
this.updateData();
}
},
});

View File

@ -1,19 +1,30 @@
import { useEvent, useState } from 'functional-mini/component';
import { mountComponent } from '../../../../src/_util/component';
const CollapseContainer = (props) => {
var _a;
const [collapse, setCollapse] = useState((_a = props.defaultCollapse) !== null && _a !== void 0 ? _a : true);
useEvent('handleToggle', () => {
setCollapse((v) => !v);
});
return {
collapse,
internalHide: props.hide,
containerTitle: props.title,
};
};
mountComponent(CollapseContainer, {
import { Component, getValueFromProps } from '../../../../src/_util/simply';
Component({
hide: false,
defaultCollapse: null,
title: '',
}, {
handleToggle() {
const { collapse } = this.data;
this.setData({
collapse: !collapse,
});
},
}, {
collapse: true,
internalHide: false,
containerTitle: '',
}, null, {
onInit() {
const [defaultCollapse, hide, title] = getValueFromProps(this, [
'defaultCollapse',
'hide',
'title',
]);
this.setData({
collapse: defaultCollapse !== null && defaultCollapse !== void 0 ? defaultCollapse : true,
internalHide: hide,
containerTitle: title,
});
},
});

View File

@ -1,27 +0,0 @@
import { alipayComponent, wechatComponent } from 'functional-mini/component';
function removeNullProps(props) {
const newProps = {};
for (const key in props) {
if (props[key] !== null) {
newProps[key] = props[key];
}
}
return newProps;
}
export function mountComponent<T>(
Hooks: (props: T) => unknown,
defaultProps: T
) {
Component(
alipayComponent(Hooks, removeNullProps(mergeDefaultProps(defaultProps)))
);
}
function mergeDefaultProps(defaultProps: Record<string, any> = {}) {
return {
...defaultProps,
};
}

View File

@ -1,77 +0,0 @@
import { useComponent } from 'functional-mini/component';
import fmtEvent from '../fmtEvent';
import { useEvent } from './useEvent';
export function useComponentEvent<T>(props: T) {
const component = useComponent();
const triggerEvent = useEvent(
(eventName: string, value: unknown, e?: any) => {
// 首字母大写,然后加上 on
const alipayCallbackName =
'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
if (props[alipayCallbackName]) {
props[alipayCallbackName](value, fmtEvent(props, e));
}
}
);
const triggerEventValues = useEvent(
(eventName: string, values: any[], e?: any) => {
// 首字母大写,然后加上 on
const alipayCallbackName =
'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
if (props[alipayCallbackName]) {
props[alipayCallbackName](...values, fmtEvent(props, e));
}
}
);
const triggerEventOnly = useEvent((eventName: string, e?: any) => {
// 首字母大写,然后加上 on
const alipayCallbackName =
'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
if (props[alipayCallbackName]) {
props[alipayCallbackName](fmtEvent(props, e));
}
});
const alipayForwardCatchEvent = useEvent((eventName: string, e: any) => {
// 首字母大写,然后加上 catch
const alipayCallbackName =
'catch' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
if (props[alipayCallbackName]) {
props[alipayCallbackName](fmtEvent(props, e));
}
});
const alipayForwardEvent = useEvent((eventName: string, e: any) => {
// 首字母大写,然后加上 on
const alipayCallbackName =
'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
if (props[alipayCallbackName]) {
props[alipayCallbackName](fmtEvent(props, e));
}
});
return {
triggerEvent,
triggerEventValues,
triggerEventOnly,
// 转发 catch 事件
alipayForwardCatchEvent,
// 转发事件
alipayForwardEvent,
};
}

View File

@ -1,18 +0,0 @@
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useEvent.ts
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as React from 'functional-mini/compat';
// eslint-disable-next-line @typescript-eslint/ban-types
export function useEvent<T extends Function>(callback: T): T {
const fnRef = React.useRef<any>();
fnRef.current = callback;
const memoFn = React.useCallback<T>(
((...args: any) => fnRef.current?.(...args)) as any,
[]
);
return memoFn;
}

View File

@ -1,56 +0,0 @@
import { useEvent } from 'functional-mini/component';
import { platform } from '../platform';
export type EventHandler<T> = (value: T, e: any) => void;
export const useHandleCustomEvent = <T>(
eventName: string,
handler: EventHandler<T>
) => {
useEvent(
eventName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(eventOrValue: any, alipayEvent?: any) => {
if (platform() === 'alipay') {
return handler(eventOrValue, alipayEvent);
}
}
);
};
export type MultipleValueEventHandler<T> = (...args: any[]) => void;
export const useMultipleValueHandleCustomEvent = <T extends any[]>(
eventName: string,
handler: MultipleValueEventHandler<T>
) => {
useEvent(
eventName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(...args: any[]) => {
if (platform() === 'alipay') {
return handler(...args);
}
}
);
};
export type EventOnlyHandler = (e: any) => void;
export const useHandleCustomEventOnly = (
eventName: string,
handler: EventOnlyHandler
) => {
useEvent(
eventName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(event) => {
if (platform() === 'alipay') {
return handler(event);
}
}
);
};

View File

@ -1,37 +0,0 @@
import { useComponent } from 'functional-mini/component';
import { getInstanceBoundingClientRect } from '../jsapi/get-instance-bounding-client-rect';
export const useInstanceBoundingClientRect = () => {
const instance = useComponent();
function getInstance() {
if (instance.$id) {
return my;
}
return instance;
}
async function getBoundingClientRectWithId(prefix: string) {
return await getInstanceBoundingClientRect(
getInstance(),
`${prefix}${instance.$id ? `-${instance.$id}` : ''}`
);
}
async function getBoundingClientRect(query: string) {
return await getInstanceBoundingClientRect(getInstance(), query);
}
async function getBoundingClientRectWithBuilder(
builder: (id: string) => string
) {
return await getInstanceBoundingClientRect(
getInstance(),
builder(instance.$id ? `-${instance.$id}` : '')
);
}
return {
getBoundingClientRect,
getBoundingClientRectWithId,
getBoundingClientRectWithBuilder,
};
};

View File

@ -1,39 +0,0 @@
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useLayoutEffect.ts
*/
import * as React from 'functional-mini/compat';
/**
* Wrap `React.useLayoutEffect` which will not throw warning message in test env
*/
const useInternalLayoutEffect = React.useEffect;
const useLayoutEffect = (callback: (mount: boolean) => void, deps?: any) => {
const firstMountRef = React.useRef(true);
useInternalLayoutEffect(() => {
return callback(firstMountRef.current);
}, deps);
// We tell react that first mount has passed
useInternalLayoutEffect(() => {
firstMountRef.current = false;
return () => {
firstMountRef.current = true;
};
}, []);
};
export const useComponentUpdateEffect: typeof React.useEffect = (
callback,
deps
) => {
useLayoutEffect((firstMount) => {
if (!firstMount) {
return callback();
}
}, deps);
};
export default useLayoutEffect;

View File

@ -1,81 +0,0 @@
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useMergedState.ts
*/
import { useEvent } from './useEvent';
import { useComponentUpdateEffect } from './useLayoutEffect';
import { useSafeState as useState } from './useState';
import { supportUndefinedProperty } from '../platform';
type Updater<T> = (
updater: T | ((origin: T) => T),
ignoreDestroy?: boolean
) => void;
/** We only think `undefined` is empty */
export function hasValue(value: any) {
if (supportUndefinedProperty()) {
return value !== undefined;
}
return value !== null && value !== undefined;
}
/**
* Similar to `useState` but will use props value if provided.
* Note that internal use rc-util `useState` hook.
*/
export function useMergedState<T, R = T>(
defaultStateValue: T | (() => T),
option?: {
defaultValue?: T | (() => T);
value?: T;
onChange?: (value: T, prevValue: T) => void;
postState?: (value: T) => T;
}
): [R, Updater<T>] {
const { defaultValue, value, onChange, postState } = option || {};
// ======================= Init =======================
const [innerValue, setInnerValue] = useState<T>(() => {
if (hasValue(value)) {
return value;
} else if (hasValue(defaultValue)) {
return typeof defaultValue === 'function'
? (defaultValue as any)()
: defaultValue;
} else {
return typeof defaultStateValue === 'function'
? (defaultStateValue as any)()
: defaultStateValue;
}
});
const mergedValue = hasValue(value) ? value : innerValue;
const postMergedValue = postState ? postState(mergedValue) : mergedValue;
// ====================== Change ======================
const onChangeFn = useEvent(onChange);
const [prevValue, setPrevValue] = useState<[T]>([mergedValue]);
useComponentUpdateEffect(() => {
const prev = prevValue[0];
if (innerValue !== prev) {
onChangeFn(innerValue, prev);
}
}, [prevValue]);
// Sync value back to `undefined` when it from control to un-control
useComponentUpdateEffect(() => {
if (!hasValue(value)) {
setInnerValue(value);
}
}, [value]);
// ====================== Update ======================
const triggerChange: Updater<T> = useEvent((updater, ignoreDestroy) => {
setInnerValue(updater, ignoreDestroy);
setPrevValue([mergedValue], ignoreDestroy);
});
return [postMergedValue as unknown as R, triggerChange];
}

View File

@ -1,109 +0,0 @@
import { useEvent } from './useEvent';
import { useComponentUpdateEffect } from './useLayoutEffect';
import { hasValue } from './useMergedState';
import { useSafeState as useState } from './useState';
type Updater<T> = (
updater: T | ((old: T) => T),
ignoreDestroy?: boolean
) => void;
export function useMixState<T, R = T, O = undefined>(
defaultStateValue: T | (() => T),
option?: {
defaultValue?: T | (() => T);
value?: T;
postState?: (
value: T,
option?: O
) => { valid: true; value: T } | { valid: false };
}
): [
R,
{
isControlled: boolean;
triggerUpdater: (value: (old: T) => T, option?: O) => void;
update(
value: T,
option?: O
): { changed: true; newValue: T } | { changed: false };
}
] {
const {
defaultValue,
value,
postState = (v) => ({ valid: true, value: v }),
} = option || {};
// ======================= Init =======================
const [innerValue, setInnerValue] = useState<T>(() => {
let v;
if (hasValue(value)) {
v = value;
} else if (hasValue(defaultValue)) {
v =
typeof defaultValue === 'function'
? (defaultValue as any)()
: defaultValue;
} else {
v =
typeof defaultStateValue === 'function'
? (defaultStateValue as any)()
: defaultStateValue;
}
const state = postState(v);
if (state.valid) {
return state.value;
}
});
const state = postState(value);
const merge = hasValue(value) && state.valid ? state.value : innerValue;
useComponentUpdateEffect(() => {
const state = postState(value);
if (state.valid) {
setInnerValue(state.value);
}
}, [value]);
const isControlled = hasValue(value);
const triggerChange: Updater<T> = useEvent((newState, ignoreDestroy) => {
setInnerValue(newState, ignoreDestroy);
});
const triggerUpdate = useEvent((value, option) => {
const state = postState(value, option);
if (state.valid && state.value !== innerValue) {
triggerChange(state.value);
return { changed: true, newValue: state.value };
}
return { changed: false };
});
const triggerUpdater: (value: (old: T) => T, option?: O) => void = useEvent(
(getValue, option) => {
if (isControlled) {
getValue(merge);
} else {
triggerChange((old: T): T => {
const newValue = getValue(old);
const state = postState(newValue, option);
if (state.valid && state.value !== innerValue) {
return state.value;
}
return old;
});
}
}
);
return [
merge as unknown as R,
{
isControlled,
update: triggerUpdate as any,
triggerUpdater,
},
];
}

View File

@ -1,4 +0,0 @@
import { useComponent, useEffect } from 'functional-mini/component';
export const triggerRefEvent = () => {
};

View File

@ -1,46 +0,0 @@
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useState.ts
*/
import * as React from 'functional-mini/compat';
type Updater<T> = T | ((prevValue: T) => T);
export type SetState<T> = (
nextValue: Updater<T>,
/**
* Will not update state when destroyed.
* Developer should make sure this is safe to ignore.
*/
ignoreDestroy?: boolean
) => void;
/**
* Same as React.useState but `setState` accept `ignoreDestroy` param to not to setState after destroyed.
* We do not make this auto is to avoid real memory leak.
* Developer should confirm it's safe to ignore themselves.
*/
export function useSafeState<T>(
defaultValue?: T | (() => T)
): [T, SetState<T>] {
const destroyRef = React.useRef(false);
const [value, setValue] = React.useState(defaultValue);
React.useEffect(() => {
destroyRef.current = false;
return () => {
destroyRef.current = true;
};
}, []);
function safeSetState(updater: Updater<T>, ignoreDestroy?: boolean) {
if (ignoreDestroy && destroyRef.current) {
return;
}
setValue(updater);
}
return [value, safeSetState];
}

View File

@ -1,6 +0,0 @@
export function triggerComponentEvent(instance, propsName, event) {
if (instance.props[propsName]) {
instance.props[propsName](event);
}
}

View File

@ -1,21 +1,41 @@
import dayjs from 'dayjs';
import equal from 'fast-deep-equal';
import Converter from './js-calendar-converter';
import { mountComponent } from '../../../../../src/_util/component';
const CollapseContainer = (props) => {
var _a, _b, _c;
const time = dayjs((_a = props.cell) === null || _a === void 0 ? void 0 : _a.time);
const vs = Converter.solar2lunar(time.get('year'), time.get('month') + 1, time.get('date'));
if (vs === -1) {
return {
cnday: '',
};
}
return {
cnday: vs.lunarFestival || vs.festival || vs.IDayCn,
festival: !!vs.festival || !!vs.lunarFestival,
unset: ((_b = props.cell) === null || _b === void 0 ? void 0 : _b.isSelectedBegin) || ((_c = props.cell) === null || _c === void 0 ? void 0 : _c.isSelectedEnd),
};
};
mountComponent(CollapseContainer, {
import { Component, getValueFromProps } from '../../../../../src/_util/simply';
Component({
cell: null,
}, {
updateData() {
const cell = getValueFromProps(this, 'cell');
const time = dayjs(cell === null || cell === void 0 ? void 0 : cell.time);
const vs = Converter.solar2lunar(time.get('year'), time.get('month') + 1, time.get('date'));
if (vs === -1) {
this.setData({
cnday: '',
});
return;
}
this.setData({
cnday: vs.lunarFestival || vs.festival || vs.IDayCn,
festival: !!vs.festival || !!vs.lunarFestival,
unset: (cell === null || cell === void 0 ? void 0 : cell.isSelectedBegin) || (cell === null || cell === void 0 ? void 0 : cell.isSelectedEnd),
});
},
}, {
cnday: '',
festival: '',
unset: '',
}, null, {
attached() {
this.updateData();
},
observers: {
'**': function (data) {
const prevData = this._prevData || this.data;
this._prevData = { ...data };
if (!equal(prevData.cell, data.cell)) {
this.updateData();
}
},
},
});

View File

@ -1,19 +1,30 @@
import { useEvent, useState } from 'functional-mini/component';
import { mountComponent } from '../../../../src/_util/component';
const CollapseContainer = (props) => {
var _a;
const [collapse, setCollapse] = useState((_a = props.defaultCollapse) !== null && _a !== void 0 ? _a : true);
useEvent('handleToggle', () => {
setCollapse((v) => !v);
});
return {
collapse,
internalHide: props.hide,
containerTitle: props.title,
};
};
mountComponent(CollapseContainer, {
import { Component, getValueFromProps } from '../../../../src/_util/simply';
Component({
hide: false,
defaultCollapse: null,
title: '',
}, {
handleToggle() {
const { collapse } = this.data;
this.setData({
collapse: !collapse,
});
},
}, {
collapse: true,
internalHide: false,
containerTitle: '',
}, null, {
attached() {
const [defaultCollapse, hide, title] = getValueFromProps(this, [
'defaultCollapse',
'hide',
'title',
]);
this.setData({
collapse: defaultCollapse !== null && defaultCollapse !== void 0 ? defaultCollapse : true,
internalHide: hide,
containerTitle: title,
});
},
});

View File

@ -4,7 +4,6 @@
"async-validator": "^4.0.7",
"dayjs": "^1.11.10",
"fast-deep-equal": "3.1.3",
"functional-mini": "^0.17.0",
"tslib": "2.5.0"
},
"repository": "git@github.com:ant-design/ant-design-mini.git"

View File

@ -1,34 +0,0 @@
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import { wechatComponent } from 'functional-mini/component';
function removeNullProps(props) {
var newProps = {};
for (var key in props) {
if (props[key] !== null) {
newProps[key] = props[key];
}
}
return newProps;
}
export function mountComponent(Hooks, defaultProps) {
Component(wechatComponent(Hooks, mergeDefaultProps(defaultProps), {
options: {
styleIsolation: 'shared',
multipleSlots: true,
virtualHost: true,
},
}));
}
function mergeDefaultProps(defaultProps) {
if (defaultProps === void 0) { defaultProps = {}; }
return __assign({ className: '', style: '' }, defaultProps);
}

View File

@ -1,32 +0,0 @@
import { useComponent } from 'functional-mini/component';
import { useEvent } from './useEvent';
export function useComponentEvent(props) {
var component = useComponent();
var triggerEvent = useEvent(function (eventName, value, e) {
// 首字母大写,然后加上 on
component.triggerEvent(eventName.toLocaleLowerCase(), value);
});
var triggerEventValues = useEvent(function (eventName, values, e) {
// 首字母大写,然后加上 on
component.triggerEvent(eventName.toLocaleLowerCase(), values);
});
var triggerEventOnly = useEvent(function (eventName, e) {
// 首字母大写,然后加上 on
component.triggerEvent(eventName.toLocaleLowerCase());
});
var alipayForwardCatchEvent = useEvent(function (eventName, e) {
// 首字母大写,然后加上 catch
});
var alipayForwardEvent = useEvent(function (eventName, e) {
// 首字母大写,然后加上 on
});
return {
triggerEvent: triggerEvent,
triggerEventValues: triggerEventValues,
triggerEventOnly: triggerEventOnly,
// 转发 catch 事件
alipayForwardCatchEvent: alipayForwardCatchEvent,
// 转发事件
alipayForwardEvent: alipayForwardEvent,
};
}

View File

@ -1,28 +0,0 @@
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useEvent.ts
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as React from 'functional-mini/compat';
// eslint-disable-next-line @typescript-eslint/ban-types
export function useEvent(callback) {
var fnRef = React.useRef();
fnRef.current = callback;
var memoFn = React.useCallback((function () {
var _a;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return (_a = fnRef.current) === null || _a === void 0 ? void 0 : _a.call.apply(_a, __spreadArray([fnRef], args, false));
}), []);
return memoFn;
}

View File

@ -1,33 +0,0 @@
import { useEvent } from 'functional-mini/component';
export var useHandleCustomEvent = function (eventName, handler) {
useEvent(eventName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function (eventOrValue, alipayEvent) {
return handler(eventOrValue.detail, eventOrValue);
});
};
export var useMultipleValueHandleCustomEvent = function (eventName, handler) {
useEvent(eventName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var firstArgs = args[0];
if (Array.isArray(firstArgs.detail)) {
var wechatArgs = firstArgs.detail.concat(firstArgs);
return handler.apply(void 0, wechatArgs);
}
else {
return handler([firstArgs.detail, firstArgs]);
}
});
};
export var useHandleCustomEventOnly = function (eventName, handler) {
useEvent(eventName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function (event) {
return handler(event);
});
};

View File

@ -1,82 +0,0 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
import { useComponent } from 'functional-mini/component';
import { getInstanceBoundingClientRect } from '../jsapi/get-instance-bounding-client-rect';
export var useInstanceBoundingClientRect = function () {
var instance = useComponent();
function getInstance() {
if (instance.$id) {
return my;
}
return instance;
}
function getBoundingClientRectWithId(prefix) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, getInstanceBoundingClientRect(getInstance(), "".concat(prefix).concat(instance.$id ? "-".concat(instance.$id) : ''))];
case 1: return [2 /*return*/, _a.sent()];
}
});
});
}
function getBoundingClientRect(query) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, getInstanceBoundingClientRect(getInstance(), query)];
case 1: return [2 /*return*/, _a.sent()];
}
});
});
}
function getBoundingClientRectWithBuilder(builder) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, getInstanceBoundingClientRect(getInstance(), builder(instance.$id ? "-".concat(instance.$id) : ''))];
case 1: return [2 /*return*/, _a.sent()];
}
});
});
}
return {
getBoundingClientRect: getBoundingClientRect,
getBoundingClientRectWithId: getBoundingClientRectWithId,
getBoundingClientRectWithBuilder: getBoundingClientRectWithBuilder,
};
};

View File

@ -1,29 +0,0 @@
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useLayoutEffect.ts
*/
import * as React from 'functional-mini/compat';
/**
* Wrap `React.useLayoutEffect` which will not throw warning message in test env
*/
var useInternalLayoutEffect = React.useEffect;
var useLayoutEffect = function (callback, deps) {
var firstMountRef = React.useRef(true);
useInternalLayoutEffect(function () {
return callback(firstMountRef.current);
}, deps);
// We tell react that first mount has passed
useInternalLayoutEffect(function () {
firstMountRef.current = false;
return function () {
firstMountRef.current = true;
};
}, []);
};
export var useComponentUpdateEffect = function (callback, deps) {
useLayoutEffect(function (firstMount) {
if (!firstMount) {
return callback();
}
}, deps);
};
export default useLayoutEffect;

View File

@ -1,60 +0,0 @@
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useMergedState.ts
*/
import { useEvent } from './useEvent';
import { useComponentUpdateEffect } from './useLayoutEffect';
import { useSafeState as useState } from './useState';
import { supportUndefinedProperty } from '../platform';
/** We only think `undefined` is empty */
export function hasValue(value) {
if (supportUndefinedProperty()) {
return value !== undefined;
}
return value !== null && value !== undefined;
}
/**
* Similar to `useState` but will use props value if provided.
* Note that internal use rc-util `useState` hook.
*/
export function useMergedState(defaultStateValue, option) {
var _a = option || {}, defaultValue = _a.defaultValue, value = _a.value, onChange = _a.onChange, postState = _a.postState;
// ======================= Init =======================
var _b = useState(function () {
if (hasValue(value)) {
return value;
}
else if (hasValue(defaultValue)) {
return typeof defaultValue === 'function'
? defaultValue()
: defaultValue;
}
else {
return typeof defaultStateValue === 'function'
? defaultStateValue()
: defaultStateValue;
}
}), innerValue = _b[0], setInnerValue = _b[1];
var mergedValue = hasValue(value) ? value : innerValue;
var postMergedValue = postState ? postState(mergedValue) : mergedValue;
// ====================== Change ======================
var onChangeFn = useEvent(onChange);
var _c = useState([mergedValue]), prevValue = _c[0], setPrevValue = _c[1];
useComponentUpdateEffect(function () {
var prev = prevValue[0];
if (innerValue !== prev) {
onChangeFn(innerValue, prev);
}
}, [prevValue]);
// Sync value back to `undefined` when it from control to un-control
useComponentUpdateEffect(function () {
if (!hasValue(value)) {
setInnerValue(value);
}
}, [value]);
// ====================== Update ======================
var triggerChange = useEvent(function (updater, ignoreDestroy) {
setInnerValue(updater, ignoreDestroy);
setPrevValue([mergedValue], ignoreDestroy);
});
return [postMergedValue, triggerChange];
}

View File

@ -1,73 +0,0 @@
import { useEvent } from './useEvent';
import { useComponentUpdateEffect } from './useLayoutEffect';
import { hasValue } from './useMergedState';
import { useSafeState as useState } from './useState';
export function useMixState(defaultStateValue, option) {
var _a = option || {}, defaultValue = _a.defaultValue, value = _a.value, _b = _a.postState, postState = _b === void 0 ? function (v) { return ({ valid: true, value: v }); } : _b;
// ======================= Init =======================
var _c = useState(function () {
var v;
if (hasValue(value)) {
v = value;
}
else if (hasValue(defaultValue)) {
v =
typeof defaultValue === 'function'
? defaultValue()
: defaultValue;
}
else {
v =
typeof defaultStateValue === 'function'
? defaultStateValue()
: defaultStateValue;
}
var state = postState(v);
if (state.valid) {
return state.value;
}
}), innerValue = _c[0], setInnerValue = _c[1];
var state = postState(value);
var merge = hasValue(value) && state.valid ? state.value : innerValue;
useComponentUpdateEffect(function () {
var state = postState(value);
if (state.valid) {
setInnerValue(state.value);
}
}, [value]);
var isControlled = hasValue(value);
var triggerChange = useEvent(function (newState, ignoreDestroy) {
setInnerValue(newState, ignoreDestroy);
});
var triggerUpdate = useEvent(function (value, option) {
var state = postState(value, option);
if (state.valid && state.value !== innerValue) {
triggerChange(state.value);
return { changed: true, newValue: state.value };
}
return { changed: false };
});
var triggerUpdater = useEvent(function (getValue, option) {
if (isControlled) {
getValue(merge);
}
else {
triggerChange(function (old) {
var newValue = getValue(old);
var state = postState(newValue, option);
if (state.valid && state.value !== innerValue) {
return state.value;
}
return old;
});
}
});
return [
merge,
{
isControlled: isControlled,
update: triggerUpdate,
triggerUpdater: triggerUpdater,
},
];
}

View File

@ -1,7 +0,0 @@
import { useComponent, useEffect } from 'functional-mini/component';
export var triggerRefEvent = function () {
var component = useComponent();
useEffect(function () {
component.triggerEvent('ref', component);
}, []);
};

View File

@ -1,26 +0,0 @@
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useState.ts
*/
import * as React from 'functional-mini/compat';
/**
* Same as React.useState but `setState` accept `ignoreDestroy` param to not to setState after destroyed.
* We do not make this auto is to avoid real memory leak.
* Developer should confirm it's safe to ignore themselves.
*/
export function useSafeState(defaultValue) {
var destroyRef = React.useRef(false);
var _a = React.useState(defaultValue), value = _a[0], setValue = _a[1];
React.useEffect(function () {
destroyRef.current = false;
return function () {
destroyRef.current = true;
};
}, []);
function safeSetState(updater, ignoreDestroy) {
if (ignoreDestroy && destroyRef.current) {
return;
}
setValue(updater);
}
return [value, safeSetState];
}

View File

@ -1,3 +0,0 @@
export function triggerComponentEvent(instance, propsName, event) {
instance.triggerEvent(propsName, event.detail);
}

View File

@ -1,31 +1,69 @@
import dayjs from 'dayjs';
import equal from 'fast-deep-equal';
import Converter from './js-calendar-converter';
import { mountComponent } from '../../../../../src/_util/component';
import { Component, getValueFromProps } from '../../../../../src/_util/simply';
interface Props {
cell: any;
}
const CollapseContainer = (props: Props) => {
const time = dayjs(props.cell?.time);
const vs = Converter.solar2lunar(
time.get('year'),
time.get('month') + 1,
time.get('date')
);
if (vs === -1) {
return {
cnday: '',
};
Component(
{
cell: null,
} as Props,
{
updateData() {
const cell = getValueFromProps(this, 'cell');
const time = dayjs(cell?.time);
const vs = Converter.solar2lunar(
time.get('year'),
time.get('month') + 1,
time.get('date')
);
if (vs === -1) {
this.setData({
cnday: '',
});
return;
}
this.setData({
cnday: vs.lunarFestival || vs.festival || vs.IDayCn,
festival: !!vs.festival || !!vs.lunarFestival,
unset: cell?.isSelectedBegin || cell?.isSelectedEnd,
});
},
},
{
cnday: '',
festival: '',
unset: '',
},
null,
{
/// #if ALIPAY
onInit() {
this.updateData();
},
didUpdate(prevProps) {
const cell = getValueFromProps(this, 'cell');
if (!equal(prevProps.cell, cell)) {
this.updateData();
}
},
/// #endif
/// #if WECHAT
attached() {
this.updateData();
},
observers: {
'**': function (data) {
const prevData = this._prevData || this.data;
this._prevData = { ...data };
if (!equal(prevData.cell, data.cell)) {
this.updateData();
}
},
},
/// #endif
}
return {
cnday: vs.lunarFestival || vs.festival || vs.IDayCn,
festival: !!vs.festival || !!vs.lunarFestival,
unset: props.cell?.isSelectedBegin || props.cell?.isSelectedEnd,
};
};
mountComponent(CollapseContainer, {
cell: null,
});
);

View File

@ -1,5 +1,4 @@
import { useEvent, useState } from 'functional-mini/component';
import { mountComponent } from '../../../../src/_util/component';
import { Component, getValueFromProps } from '../../../../src/_util/simply';
export interface Props {
hide?: boolean;
@ -8,22 +7,55 @@ export interface Props {
handleClick?(id: string): void;
}
const CollapseContainer = (props: Props) => {
const [collapse, setCollapse] = useState(props.defaultCollapse ?? true);
Component(
{
hide: false,
defaultCollapse: null,
title: '',
} as Props,
{
handleToggle() {
const { collapse } = this.data;
this.setData({
collapse: !collapse,
});
},
},
{
collapse: true,
internalHide: false,
containerTitle: '',
},
null,
{
/// #if ALIPAY
onInit() {
const [defaultCollapse, hide, title] = getValueFromProps(this, [
'defaultCollapse',
'hide',
'title',
]);
this.setData({
collapse: defaultCollapse ?? true,
internalHide: hide,
containerTitle: title,
});
},
/// #endif
/// #if WECHAT
attached() {
const [defaultCollapse, hide, title] = getValueFromProps(this, [
'defaultCollapse',
'hide',
'title',
]);
this.setData({
collapse: defaultCollapse ?? true,
internalHide: hide,
containerTitle: title,
});
},
useEvent('handleToggle', () => {
setCollapse((v) => !v);
});
return {
collapse,
internalHide: props.hide,
containerTitle: props.title,
};
};
mountComponent<Props>(CollapseContainer, {
hide: false,
defaultCollapse: null,
title: '',
});
/// #endif
}
);

View File

@ -64,14 +64,6 @@ $ npm run dev:doc
## Ant Design Mini 的工程方案
### 函数式组件
从 v2 版本开始,我们逐步采用“React 函数式组件”开发模式来开发小程序自定义组件,背后依托 [functional-mini](https://github.com/ant-design/functional-mini) 这个 SDK。如日历组件(参见 [Calendar/index.ts](https://github.com/ant-design/ant-design-mini/blob/master/src/Calendar/index.ts))。
[functional-mini](https://github.com/ant-design/functional-mini) 作为运行时 SDK,接管小程序的逻辑层代码,但并不影响视图层,为我们在项目架构复杂度和编码习惯上带来平衡。借此,函数式组件的基本特性得以运用,提升代码可维护性,如数据加工逻辑组装、hooks 逻辑复用等。
欢迎你一同参与 Ant Design Mini 函数式组件开发,探索更佳的小程序工程形态。
### 使用 tsx 语法编写视图层
我们使用 tsx 语法编写视图层。编译器解析 tsx 语法后,生成小程序视图层代码。这意味着:

View File

@ -35,7 +35,6 @@
"async-validator": "^4.0.7",
"dayjs": "^1.11.3",
"fast-deep-equal": "3.1.3",
"functional-mini": "^0.17.0",
"tslib": "2.5.0"
},
"overrides": {

View File

@ -1,44 +0,0 @@
import { alipayComponent, wechatComponent } from 'functional-mini/component';
function removeNullProps(props) {
const newProps = {};
for (const key in props) {
if (props[key] !== null) {
newProps[key] = props[key];
}
}
return newProps;
}
export function mountComponent<T>(
Hooks: (props: T) => unknown,
defaultProps: T
) {
/// #if WECHAT
Component(
wechatComponent(Hooks, mergeDefaultProps(defaultProps) as unknown as T, {
options: {
styleIsolation: 'shared',
multipleSlots: true,
virtualHost: true,
},
})
);
/// #endif
/// #if ALIPAY
Component(
alipayComponent(Hooks, removeNullProps(mergeDefaultProps(defaultProps)))
);
/// #endif
}
function mergeDefaultProps(defaultProps: Record<string, any> = {}) {
return {
/// #if WECHAT
className: '',
style: '',
/// #endif
...defaultProps,
};
}

View File

@ -1,96 +0,0 @@
import { useComponent } from 'functional-mini/component';
import fmtEvent from '../fmtEvent';
import { useEvent } from './useEvent';
export function useComponentEvent<T>(props: T) {
const component = useComponent();
const triggerEvent = useEvent(
(eventName: string, value: unknown, e?: any) => {
// 首字母大写,然后加上 on
/// #if ALIPAY
const alipayCallbackName =
'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
if (props[alipayCallbackName]) {
props[alipayCallbackName](value, fmtEvent(props, e));
}
/// #endif
/// #if WECHAT
component.triggerEvent(eventName.toLocaleLowerCase(), value);
/// #endif
}
);
const triggerEventValues = useEvent(
(eventName: string, values: any[], e?: any) => {
// 首字母大写,然后加上 on
/// #if ALIPAY
const alipayCallbackName =
'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
if (props[alipayCallbackName]) {
props[alipayCallbackName](...values, fmtEvent(props, e));
}
/// #endif
/// #if WECHAT
component.triggerEvent(eventName.toLocaleLowerCase(), values);
/// #endif
}
);
const triggerEventOnly = useEvent((eventName: string, e?: any) => {
// 首字母大写,然后加上 on
/// #if ALIPAY
const alipayCallbackName =
'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
if (props[alipayCallbackName]) {
props[alipayCallbackName](fmtEvent(props, e));
}
/// #endif
/// #if WECHAT
component.triggerEvent(eventName.toLocaleLowerCase());
/// #endif
});
const alipayForwardCatchEvent = useEvent((eventName: string, e: any) => {
// 首字母大写,然后加上 catch
/// #if ALIPAY
const alipayCallbackName =
'catch' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
if (props[alipayCallbackName]) {
props[alipayCallbackName](fmtEvent(props, e));
}
/// #endif
});
const alipayForwardEvent = useEvent((eventName: string, e: any) => {
// 首字母大写,然后加上 on
/// #if ALIPAY
const alipayCallbackName =
'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
if (props[alipayCallbackName]) {
props[alipayCallbackName](fmtEvent(props, e));
}
/// #endif
});
return {
triggerEvent,
triggerEventValues,
triggerEventOnly,
// 转发 catch 事件
alipayForwardCatchEvent,
// 转发事件
alipayForwardEvent,
};
}

View File

@ -1,18 +0,0 @@
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useEvent.ts
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as React from 'functional-mini/compat';
// eslint-disable-next-line @typescript-eslint/ban-types
export function useEvent<T extends Function>(callback: T): T {
const fnRef = React.useRef<any>();
fnRef.current = callback;
const memoFn = React.useCallback<T>(
((...args: any) => fnRef.current?.(...args)) as any,
[]
);
return memoFn;
}

View File

@ -1,77 +0,0 @@
import { useEvent } from 'functional-mini/component';
import { platform } from '../platform';
export type EventHandler<T> = (value: T, e: any) => void;
export const useHandleCustomEvent = <T>(
eventName: string,
handler: EventHandler<T>
) => {
useEvent(
eventName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(eventOrValue: any, alipayEvent?: any) => {
/// #if ALIPAY
if (platform() === 'alipay') {
return handler(eventOrValue, alipayEvent);
}
/// #endif
/// #if WECHAT
return handler(eventOrValue.detail, eventOrValue);
/// #endif
}
);
};
export type MultipleValueEventHandler<T> = (...args: any[]) => void;
export const useMultipleValueHandleCustomEvent = <T extends any[]>(
eventName: string,
handler: MultipleValueEventHandler<T>
) => {
useEvent(
eventName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(...args: any[]) => {
/// #if ALIPAY
if (platform() === 'alipay') {
return handler(...args);
}
/// #endif
/// #if WECHAT
const firstArgs = args[0];
if (Array.isArray(firstArgs.detail)) {
const wechatArgs = firstArgs.detail.concat(firstArgs);
return handler(...wechatArgs);
} else {
return handler([firstArgs.detail, firstArgs]);
}
/// #endif
}
);
};
export type EventOnlyHandler = (e: any) => void;
export const useHandleCustomEventOnly = (
eventName: string,
handler: EventOnlyHandler
) => {
useEvent(
eventName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(event) => {
/// #if ALIPAY
if (platform() === 'alipay') {
return handler(event);
}
/// #endif
/// #if WECHAT
return handler(event);
/// #endif
}
);
};

View File

@ -1,37 +0,0 @@
import { useComponent } from 'functional-mini/component';
import { getInstanceBoundingClientRect } from '../jsapi/get-instance-bounding-client-rect';
export const useInstanceBoundingClientRect = () => {
const instance = useComponent();
function getInstance() {
if (instance.$id) {
return my;
}
return instance;
}
async function getBoundingClientRectWithId(prefix: string) {
return await getInstanceBoundingClientRect(
getInstance(),
`${prefix}${instance.$id ? `-${instance.$id}` : ''}`
);
}
async function getBoundingClientRect(query: string) {
return await getInstanceBoundingClientRect(getInstance(), query);
}
async function getBoundingClientRectWithBuilder(
builder: (id: string) => string
) {
return await getInstanceBoundingClientRect(
getInstance(),
builder(instance.$id ? `-${instance.$id}` : '')
);
}
return {
getBoundingClientRect,
getBoundingClientRectWithId,
getBoundingClientRectWithBuilder,
};
};

View File

@ -1,39 +0,0 @@
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useLayoutEffect.ts
*/
import * as React from 'functional-mini/compat';
/**
* Wrap `React.useLayoutEffect` which will not throw warning message in test env
*/
const useInternalLayoutEffect = React.useEffect;
const useLayoutEffect = (callback: (mount: boolean) => void, deps?: any) => {
const firstMountRef = React.useRef(true);
useInternalLayoutEffect(() => {
return callback(firstMountRef.current);
}, deps);
// We tell react that first mount has passed
useInternalLayoutEffect(() => {
firstMountRef.current = false;
return () => {
firstMountRef.current = true;
};
}, []);
};
export const useComponentUpdateEffect: typeof React.useEffect = (
callback,
deps
) => {
useLayoutEffect((firstMount) => {
if (!firstMount) {
return callback();
}
}, deps);
};
export default useLayoutEffect;

View File

@ -1,81 +0,0 @@
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useMergedState.ts
*/
import { useEvent } from './useEvent';
import { useComponentUpdateEffect } from './useLayoutEffect';
import { useSafeState as useState } from './useState';
import { supportUndefinedProperty } from '../platform';
type Updater<T> = (
updater: T | ((origin: T) => T),
ignoreDestroy?: boolean
) => void;
/** We only think `undefined` is empty */
export function hasValue(value: any) {
if (supportUndefinedProperty()) {
return value !== undefined;
}
return value !== null && value !== undefined;
}
/**
* Similar to `useState` but will use props value if provided.
* Note that internal use rc-util `useState` hook.
*/
export function useMergedState<T, R = T>(
defaultStateValue: T | (() => T),
option?: {
defaultValue?: T | (() => T);
value?: T;
onChange?: (value: T, prevValue: T) => void;
postState?: (value: T) => T;
}
): [R, Updater<T>] {
const { defaultValue, value, onChange, postState } = option || {};
// ======================= Init =======================
const [innerValue, setInnerValue] = useState<T>(() => {
if (hasValue(value)) {
return value;
} else if (hasValue(defaultValue)) {
return typeof defaultValue === 'function'
? (defaultValue as any)()
: defaultValue;
} else {
return typeof defaultStateValue === 'function'
? (defaultStateValue as any)()
: defaultStateValue;
}
});
const mergedValue = hasValue(value) ? value : innerValue;
const postMergedValue = postState ? postState(mergedValue) : mergedValue;
// ====================== Change ======================
const onChangeFn = useEvent(onChange);
const [prevValue, setPrevValue] = useState<[T]>([mergedValue]);
useComponentUpdateEffect(() => {
const prev = prevValue[0];
if (innerValue !== prev) {
onChangeFn(innerValue, prev);
}
}, [prevValue]);
// Sync value back to `undefined` when it from control to un-control
useComponentUpdateEffect(() => {
if (!hasValue(value)) {
setInnerValue(value);
}
}, [value]);
// ====================== Update ======================
const triggerChange: Updater<T> = useEvent((updater, ignoreDestroy) => {
setInnerValue(updater, ignoreDestroy);
setPrevValue([mergedValue], ignoreDestroy);
});
return [postMergedValue as unknown as R, triggerChange];
}

View File

@ -1,109 +0,0 @@
import { useEvent } from './useEvent';
import { useComponentUpdateEffect } from './useLayoutEffect';
import { hasValue } from './useMergedState';
import { useSafeState as useState } from './useState';
type Updater<T> = (
updater: T | ((old: T) => T),
ignoreDestroy?: boolean
) => void;
export function useMixState<T, R = T, O = undefined>(
defaultStateValue: T | (() => T),
option?: {
defaultValue?: T | (() => T);
value?: T;
postState?: (
value: T,
option?: O
) => { valid: true; value: T } | { valid: false };
}
): [
R,
{
isControlled: boolean;
triggerUpdater: (value: (old: T) => T, option?: O) => void;
update(
value: T,
option?: O
): { changed: true; newValue: T } | { changed: false };
}
] {
const {
defaultValue,
value,
postState = (v) => ({ valid: true, value: v }),
} = option || {};
// ======================= Init =======================
const [innerValue, setInnerValue] = useState<T>(() => {
let v;
if (hasValue(value)) {
v = value;
} else if (hasValue(defaultValue)) {
v =
typeof defaultValue === 'function'
? (defaultValue as any)()
: defaultValue;
} else {
v =
typeof defaultStateValue === 'function'
? (defaultStateValue as any)()
: defaultStateValue;
}
const state = postState(v);
if (state.valid) {
return state.value;
}
});
const state = postState(value);
const merge = hasValue(value) && state.valid ? state.value : innerValue;
useComponentUpdateEffect(() => {
const state = postState(value);
if (state.valid) {
setInnerValue(state.value);
}
}, [value]);
const isControlled = hasValue(value);
const triggerChange: Updater<T> = useEvent((newState, ignoreDestroy) => {
setInnerValue(newState, ignoreDestroy);
});
const triggerUpdate = useEvent((value, option) => {
const state = postState(value, option);
if (state.valid && state.value !== innerValue) {
triggerChange(state.value);
return { changed: true, newValue: state.value };
}
return { changed: false };
});
const triggerUpdater: (value: (old: T) => T, option?: O) => void = useEvent(
(getValue, option) => {
if (isControlled) {
getValue(merge);
} else {
triggerChange((old: T): T => {
const newValue = getValue(old);
const state = postState(newValue, option);
if (state.valid && state.value !== innerValue) {
return state.value;
}
return old;
});
}
}
);
return [
merge as unknown as R,
{
isControlled,
update: triggerUpdate as any,
triggerUpdater,
},
];
}

View File

@ -1,10 +0,0 @@
import { useComponent, useEffect } from 'functional-mini/component';
export const triggerRefEvent = () => {
/// #if WECHAT
const component = useComponent();
useEffect(() => {
component.triggerEvent('ref', component);
}, []);
/// #endif
};

View File

@ -1,46 +0,0 @@
/**
* copy from https://github.com/react-component/util/blob/9d5cb8946da29e690bead78b2c251da6f7cbd0eb/src/hooks/useState.ts
*/
import * as React from 'functional-mini/compat';
type Updater<T> = T | ((prevValue: T) => T);
export type SetState<T> = (
nextValue: Updater<T>,
/**
* Will not update state when destroyed.
* Developer should make sure this is safe to ignore.
*/
ignoreDestroy?: boolean
) => void;
/**
* Same as React.useState but `setState` accept `ignoreDestroy` param to not to setState after destroyed.
* We do not make this auto is to avoid real memory leak.
* Developer should confirm it's safe to ignore themselves.
*/
export function useSafeState<T>(
defaultValue?: T | (() => T)
): [T, SetState<T>] {
const destroyRef = React.useRef(false);
const [value, setValue] = React.useState(defaultValue);
React.useEffect(() => {
destroyRef.current = false;
return () => {
destroyRef.current = true;
};
}, []);
function safeSetState(updater: Updater<T>, ignoreDestroy?: boolean) {
if (ignoreDestroy && destroyRef.current) {
return;
}
setValue(updater);
}
return [value, safeSetState];
}

View File

@ -1,11 +0,0 @@
export function triggerComponentEvent(instance, propsName, event) {
/// #if WECHAT
instance.triggerEvent(propsName, event.detail);
/// #endif
/// #if ALIPAY
if (instance.props[propsName]) {
instance.props[propsName](event);
}
/// #endif
}

View File

@ -1,122 +0,0 @@
import { useEvent, useState, alipayComponent } from 'functional-mini/component';
import { sleep, createInstance } from '../../../utils';
import { expect, it } from 'vitest';
import {
useHandleCustomEvent,
useMultipleValueHandleCustomEvent,
useHandleCustomEventOnly,
} from 'compiled-alipay/_util/hooks/useHandleCustomEvent';
import fmtEvent from 'compiled-alipay/_util/fmtEvent';
const testMy = {
canIUse() {
return true;
},
};
it('test useHandleCustomEvent', async () => {
const Test = () => {
const [count, setCount] = useState(1);
useHandleCustomEvent('handleEvent', (value, event) => {
return {
value,
event,
count,
};
});
useEvent('updateCount', function (v) {
setCount(v);
return 'ok';
});
return {
count,
};
};
const componentOptions = alipayComponent(Test, {});
const instance = createInstance(componentOptions as any, {}, testMy);
expect(
instance.callMethod('handleEvent', 2, fmtEvent({ 'data-a': 1 })).count
).toBe(1);
expect(instance.callMethod('updateCount', 10)).toEqual('ok');
await sleep(20);
expect(instance.getData().count).toEqual(10);
expect(
instance.callMethod('handleEvent', 2, fmtEvent({ 'data-a': 1 }))
).toEqual({
value: 2,
event: {
currentTarget: { dataset: { 'a': 1 } },
target: { dataset: { 'a': 1 }, targetDataset: { 'a': 1 } },
},
count: 10,
});
expect(instance.callMethod('updateCount', 30)).toEqual('ok');
await sleep(20);
expect(instance.getData().count).toEqual(30);
});
it('test useMultipleValueHandleCustomEvent', async () => {
const Test = () => {
useMultipleValueHandleCustomEvent('handleEvent', (...args) => {
return {
args,
};
});
return {};
};
const componentOptions = alipayComponent(Test, {});
const instance = createInstance(componentOptions as any, {}, testMy);
expect(
instance.callMethod('handleEvent', 1, 2, fmtEvent({ 'data-a': 1 }))
).toEqual({
args: [
1,
2,
{
currentTarget: { dataset: { 'a': 1 } },
target: { dataset: { 'a': 1 }, targetDataset: { 'a': 1 } },
},
],
});
expect(instance.callMethod('handleEvent')).toEqual({
args: [],
});
expect(instance.callMethod('handleEvent', fmtEvent({ 'data-a': 1 }))).toEqual(
{
args: [
{
currentTarget: { dataset: { 'a': 1 } },
target: { dataset: { 'a': 1 }, targetDataset: { 'a': 1 } },
},
],
}
);
});
it('test useHandleCustomEventOnly', async () => {
const Test = () => {
useHandleCustomEventOnly('handleEvent', (...args) => {
return {
args,
};
});
return {};
};
const componentOptions = alipayComponent(Test, {}) as any;
const instance = createInstance(componentOptions, {}, testMy);
expect(instance.callMethod('handleEvent', fmtEvent({ 'data-a': 1 }))).toEqual(
{
args: [
{
currentTarget: { dataset: { 'a': 1 } },
target: { dataset: { 'a': 1 }, targetDataset: { 'a': 1 } },
},
],
}
);
});

View File

@ -10,11 +10,6 @@ export default defineConfig({
},
},
test: {
server: {
deps: {
inline: [/functional-mini/],
},
},
watch: true,
globals: true,
setupFiles: ['./tests/setup.ts'],