diff --git a/.prettierrc b/.prettierrc index 59b1ac8..2cf1ac5 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,7 @@ { "trailingComma": "es5", "tabWidth": 4, + "printWidth": 120, "semi": true, "singleQuote": true, "overrides": [ diff --git a/src/index.js b/src/index.js index 3ce8816..5afc274 100644 --- a/src/index.js +++ b/src/index.js @@ -1,15 +1,5 @@ import Validate from '@alifd/validate'; -import { - log, - func, - getValueFromEvent, - getErrorStrs, - getParams, - setIn, - getIn, - deleteIn, - mapValidateRules, -} from './utils'; +import { getValueFromEvent, getErrorStrs, getParams, setIn, getIn, deleteIn, mapValidateRules, warning } from './utils'; const initMeta = { state: '', @@ -26,9 +16,7 @@ class Field { return (options = {}) => { const [, setState] = useState(); - const field = useMemo(() => this.create({ setState }, options), [ - setState, - ]); + const field = useMemo(() => this.create({ setState }, options), [setState]); return field; }; @@ -36,9 +24,7 @@ class Field { constructor(com, options = {}) { if (!com) { - log.warning( - '`this` is missing in `Field`, you should use like `new Field(this)`' - ); + warning('`this` is missing in `Field`, you should use like `new Field(this)`'); } this.com = com; @@ -47,7 +33,8 @@ class Field { this.instance = {}; // holds constructor values. Used for setting field defaults on init if no other value or initValue is passed. // Also used caching values when using `parseName: true` before a field is initialized - this.values = options.values || {}; + this.values = Object.assign({}, options.values); + this.processErrorMessage = options.processErrorMessage; this.afterValidateRerender = options.afterValidateRerender; @@ -57,7 +44,7 @@ class Field { forceUpdate: false, scrollToFirstError: true, first: false, - onChange: func.noop, + onChange: () => {}, autoUnmount: true, autoValidate: true, }, @@ -84,10 +71,6 @@ class Field { ].forEach(m => { this[m] = this[m].bind(this); }); - - if (options.values) { - this.setValues(options.values, false); - } } setOptions(options) { @@ -113,11 +96,7 @@ class Field { const { parseName } = this.options; const originalProps = Object.assign({}, props, rprops); - const defaultValueName = `default${valueName[0].toUpperCase()}${valueName.slice( - 1 - )}`; - - const field = this._getInitMeta(name); + const defaultValueName = `default${valueName[0].toUpperCase()}${valueName.slice(1)}`; let defaultValue; if (typeof initValue !== 'undefined') { defaultValue = initValue; @@ -126,11 +105,12 @@ class Field { defaultValue = originalProps[defaultValueName]; } + // get field from this.fieldsMeta or new one + const field = this._getInitMeta(name); Object.assign(field, { valueName, initValue: defaultValue, - disabled: - 'disabled' in originalProps ? originalProps.disabled : false, + disabled: 'disabled' in originalProps ? originalProps.disabled : false, getValueFromEvent, rules: Array.isArray(rules) ? rules : [rules], ref: originalProps.ref, @@ -148,34 +128,30 @@ class Field { } } - // should get value from this.values /** - * a new field (value not in field) - * step 1: get value from this.values - * step 2: from defaultValue + * first init field (value not in field) + * should get field.value from this.values or defaultValue */ if (!('value' in field)) { if (parseName) { const cachedValue = getIn(this.values, name); - field.value = - typeof cachedValue !== 'undefined' - ? cachedValue - : defaultValue; + if (typeof cachedValue !== 'undefined') { + field.value = cachedValue; + } else if (typeof defaultValue !== 'undefined') { + field.value = defaultValue; + this.values = setIn(this.values, name, field.value); + } } else { const cachedValue = this.values[name]; - field.value = - typeof cachedValue !== 'undefined' - ? cachedValue - : defaultValue; + if (typeof cachedValue !== 'undefined') { + field.value = cachedValue; + } else if (typeof defaultValue !== 'undefined') { + field.value = defaultValue; + this.values[name] = field.value; + } } } - if (parseName && !getIn(this.values, name)) { - this.values = setIn(this.values, name, field.value); - } else if (!parseName && !this.values[name]) { - this.values[name] = field.value; - } - // Component props const inputProps = { 'data-meta': 'Field', @@ -223,9 +199,7 @@ class Field { * props.onChange props.onBlur */ _callPropsEvent(action, props, ...args) { - action in props && - typeof props[action] === 'function' && - props[action](...args); + action in props && typeof props[action] === 'function' && props[action](...args); } _getInitMeta(name) { @@ -247,9 +221,7 @@ class Field { return; } - field.value = field.getValueFromEvent - ? field.getValueFromEvent.apply(this, others) - : getValueFromEvent(e); + field.value = field.getValueFromEvent ? field.getValueFromEvent.apply(this, others) : getValueFromEvent(e); if (this.options.parseName) { this.values = setIn(this.values, name, field.value); @@ -312,11 +284,7 @@ class Field { // 2. _saveRef(B, ref) (eg: same name but different compoent may be here) if (autoUnmount && !this.fieldsMeta[name]) { this.fieldsMeta[name] = this._getCache(name, key); - this.setValue( - name, - this.fieldsMeta[name] && this.fieldsMeta[name].value, - false - ); + this.setValue(name, this.fieldsMeta[name] && this.fieldsMeta[name].value, false); } // only one time here @@ -364,10 +332,7 @@ class Field { }, errors => { if (errors && errors.length) { - field.errors = getErrorStrs( - errors, - this.processErrorMessage - ); + field.errors = getErrorStrs(errors, this.processErrorMessage); field.state = 'error'; } else { field.errors = []; @@ -434,11 +399,7 @@ class Field { this.fieldsMeta[name].value = value; } else { // if no value then copy values from fieldsMeta to keep initialized component data - this.values = setIn( - this.values, - name, - this.fieldsMeta[name].value - ); + this.values = setIn(this.values, name, this.fieldsMeta[name].value); } }); } @@ -455,10 +416,7 @@ class Field { }; } - if ( - this.fieldsMeta[name].errors && - this.fieldsMeta[name].errors.length > 0 - ) { + if (this.fieldsMeta[name].errors && this.fieldsMeta[name].errors.length > 0) { this.fieldsMeta[name].state = 'error'; } else { this.fieldsMeta[name].state = ''; @@ -555,8 +513,7 @@ class Field { if (!hasRule) { const errors = this.formatGetErrors(fieldNames); - callback && - callback(errors, this.getValues(names ? fieldNames : [])); + callback && callback(errors, this.getValues(names ? fieldNames : [])); return; } @@ -583,10 +540,7 @@ class Field { // update error in every Field Object.keys(errorsGroup).forEach(i => { const field = this._get(i); - field.errors = getErrorStrs( - errorsGroup[i].errors, - this.processErrorMessage - ); + field.errors = getErrorStrs(errorsGroup[i].errors, this.processErrorMessage); field.state = 'error'; }); } @@ -594,29 +548,20 @@ class Field { const formattedGetErrors = this.formatGetErrors(fieldNames); if (formattedGetErrors) { - errorsGroup = Object.assign( - {}, - formattedGetErrors, - errorsGroup - ); + errorsGroup = Object.assign({}, formattedGetErrors, errorsGroup); } // update to success which has no error for (let i = 0; i < fieldNames.length; i++) { const name = fieldNames[i]; const field = this._get(name); - if ( - field && - field.rules && - !(errorsGroup && name in errorsGroup) - ) { + if (field && field.rules && !(errorsGroup && name in errorsGroup)) { field.state = 'success'; } } // eslint-disable-next-line callback-return - callback && - callback(errorsGroup, this.getValues(names ? fieldNames : [])); + callback && callback(errorsGroup, this.getValues(names ? fieldNames : [])); this._reRender(); if (typeof this.afterValidateRerender === 'function') { @@ -727,10 +672,7 @@ class Field { Object.keys(errorsGroup).forEach(i => { const field = this._get(i); if (field) { - field.errors = getErrorStrs( - errorsGroup[i].errors, - this.processErrorMessage - ); + field.errors = getErrorStrs(errorsGroup[i].errors, this.processErrorMessage); field.state = 'error'; } }); @@ -790,28 +732,14 @@ class Field { } } - reset(ns, backToDefault = false) { - if (ns === true) { - log.deprecated('reset(true)', 'resetToDefault()', 'Field'); - this.resetToDefault(); - } else if (backToDefault === true) { - log.deprecated('reset(ns,true)', 'resetToDefault(ns)', 'Field'); - this.resetToDefault(ns); - } else { - this._reset(ns, false); - } + reset(ns) { + this._reset(ns, false); } resetToDefault(ns) { this._reset(ns, true); } - // deprecated. TODO: remove in 2.0 version - isValidating(name) { - log.deprecated('isValidating', 'getState', 'Field'); - return this.getState(name) === 'loading'; - } - getNames() { const fieldsMeta = this.fieldsMeta; return Object.keys(fieldsMeta).filter(() => { @@ -847,7 +775,7 @@ class Field { */ spliceArray(keyMatch, startIndex) { if (keyMatch.indexOf('{index}') === -1) { - log.warning('{index} not find in key'); + warning('{index} not find in key'); return; } diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..ce427f5 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,197 @@ +export function getIn(state, name) { + if (!state) { + return state; + } + + const path = name + .replace(/\[/, '.') + .replace(/\]/, '') + .split('.'); + const length = path.length; + if (!length) { + return undefined; + } + + let result = state; + for (let i = 0; i < length && !!result; ++i) { + result = result[path[i]]; + } + + return result; +} + +const setInWithPath = (state, value, path, pathIndex) => { + if (pathIndex >= path.length) { + return value; + } + + const first = path[pathIndex]; + const next = setInWithPath(state && state[first], value, path, pathIndex + 1); + + if (!state) { + const initialized = isNaN(first) ? {} : []; + initialized[first] = next; + return initialized; + } + + if (Array.isArray(state)) { + const copy = [].concat(state); + copy[first] = next; + return copy; + } + + return Object.assign({}, state, { + [first]: next, + }); +}; + +export function setIn(state, name, value) { + return setInWithPath( + state, + value, + name + .replace(/\[/, '.') + .replace(/\]/, '') + .split('.'), + 0 + ); +} + +export function deleteIn(state, name) { + if (!state) { + return; + } + + const path = name + .replace(/\[/, '.') + .replace(/\]/, '') + .split('.'); + const length = path.length; + if (!length) { + return state; + } + + let result = state; + for (let i = 0; i < length && !!result; ++i) { + if (i === length - 1) { + delete result[path[i]]; + } else { + result = result[path[i]]; + } + } + + return state; +} + +export function getErrorStrs(errors, processErrorMessage) { + if (errors) { + return errors.map(e => { + const message = e.message || e; + + if (typeof processErrorMessage === 'function') { + return processErrorMessage(message); + } + + return message; + }); + } + return errors; +} + +export function getParams(ns, cb) { + let names = typeof ns === 'string' ? [ns] : ns; + let callback = cb; + if (cb === undefined && typeof names === 'function') { + callback = names; + names = undefined; + } + return { + names, + callback, + }; +} + +/** + * 从组件事件中获取数据 + * @param e Event或者value + * @returns value + */ +export function getValueFromEvent(e) { + // support custom element + if (!e || !e.target) { + return e; + } + const { target } = e; + + if (target.type === 'checkbox') { + return target.checked; + } else if (target.type === 'radio') { + //兼容原生radioGroup + if (target.value) { + return target.value; + } else { + return target.checked; + } + } + return target.value; +} + +function validateMap(rulesMap, rule, defaultTrigger) { + const nrule = Object.assign({}, rule); + + if (!nrule.trigger) { + nrule.trigger = [defaultTrigger]; + } + + if (typeof nrule.trigger === 'string') { + nrule.trigger = [nrule.trigger]; + } + + for (let i = 0; i < nrule.trigger.length; i++) { + const trigger = nrule.trigger[i]; + + if (trigger in rulesMap) { + rulesMap[trigger].push(nrule); + } else { + rulesMap[trigger] = [nrule]; + } + } + + delete nrule.trigger; +} + +/** + * 提取rule里面的trigger并且做映射 + * @param {Array} rules 规则 + * @param {String} defaultTrigger 默认触发 + * @return {Object} {onChange:rule1, onBlur: rule2} + */ +export function mapValidateRules(rules, defaultTrigger) { + const rulesMap = {}; + + rules.forEach(rule => { + validateMap(rulesMap, rule, defaultTrigger); + }); + + return rulesMap; +} + +let warn = () => {}; + +// don't print warning message when in production env or node runtime +if ( + typeof process !== 'undefined' && + process.env && + process.env.NODE_ENV !== 'production' && + typeof window !== 'undefined' && + typeof document !== 'undefined' +) { + warn = (...args) => { + /* eslint-disable no-console */ + if (typeof console !== 'undefined' && console.warn) { + console.warn(...args); + } + }; +} + +export const warning = warn; diff --git a/src/utils/deleteIn.js b/src/utils/deleteIn.js deleted file mode 100644 index a2faaf1..0000000 --- a/src/utils/deleteIn.js +++ /dev/null @@ -1,25 +0,0 @@ -export default function deleteIn(state, name) { - if (!state) { - return; - } - - const path = name - .replace(/\[/, '.') - .replace(/\]/, '') - .split('.'); - const length = path.length; - if (!length) { - return state; - } - - let result = state; - for (let i = 0; i < length && !!result; ++i) { - if (i === length - 1) { - delete result[path[i]]; - } else { - result = result[path[i]]; - } - } - - return state; -} diff --git a/src/utils/env.js b/src/utils/env.js deleted file mode 100644 index 0abc0b3..0000000 --- a/src/utils/env.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * IE浏览器的渲染引擎版本号 - * 注意:此属性与浏览器版本号不同,IE的渲染引擎版本号是可以通过HTML header或手动设置去更改的 - * @type {Number} 6 ~ 10 - */ -export const ieVersion = - typeof document !== 'undefined' ? document.documentMode : undefined; - -/** - * 判断是否是生产环境 - * @type {Boolean} - */ -export const isProduction = () => { - const PRODUCTION_ENV = 'production'; - let result = false; - try { - if (process.env.NODE_ENV === PRODUCTION_ENV) { - result = true; - } - } catch (err) { - // - } - - if (!result) { - try { - if (window.process.env.NODE_ENV === PRODUCTION_ENV) { - result = true; - } - } catch (err) { - // - } - } - - return result; -}; - -export default { - ieVersion, - isProduction, -}; diff --git a/src/utils/func.js b/src/utils/func.js deleted file mode 100644 index 945749b..0000000 --- a/src/utils/func.js +++ /dev/null @@ -1,79 +0,0 @@ -import { isPromise } from './object'; - -/** - * 一个空方法,返回入参本身或空对象 - */ -export const noop = () => {}; - -/** - * 一个空方法,返回false - */ -export const prevent = () => false; - -/** - * 将N个方法合并为一个链式调用的方法 - * @return {Function} 合并后的方法 - * 参考 https://github.com/react-component/util/ - * - * @example - * func.makeChain(this.handleChange, this.props.onChange); - */ -export function makeChain(...fns) { - if (fns.length === 1) { - return fns[0]; - } - - return function chainedFunction(...args) { - for (let i = 0, j = fns.length; i < j; i++) { - if (fns[i] && fns[i].apply) { - fns[i].apply(this, args); - } - } - }; -} - -/** - * 批量改变方法的上下文 - * 此方法在react组件中很有用,在constructor中批量将组件上的方法执行上下文绑定到组件本身 - * 注意:用bind改变函数运行的上下文只会生效一次 - * @param {Object} ctx 方法挂载的对象以及执行的上下文 - * @param {Array} fns 方法名列表 - * - * @example - * func.bindCtx(this, ['handleClick', 'handleChange']); - */ -export function bindCtx(ctx, fns, ns) { - if (typeof fns === 'string') { - fns = [fns]; - } - - // 方法的挂载空间,如果不传,默认与ctx相同 - ns = ns || ctx; - - fns.forEach(fnName => { - // 这里不要添加空方法判断,由调用者保证正确性,否则出了问题无法排查 - ns[fnName] = ns[fnName].bind(ctx); - }); -} - -/** - * 用于执行回调方法后的逻辑 - * @param {*} ret 回调方法执行结果 - * @param {Function} success 执行结果返回非false的回调 - * @param {Function} [failure=noop] 执行结果返回false的回调 - */ -export function promiseCall(ret, success, failure = noop) { - if (isPromise(ret)) { - return ret - .then(result => { - success(result); - return result; - }) - .catch(e => { - failure(e); - // throw e; - }); - } - - return ret !== false ? success(ret) : failure(ret); -} diff --git a/src/utils/getErrorStrs.js b/src/utils/getErrorStrs.js deleted file mode 100644 index ac07367..0000000 --- a/src/utils/getErrorStrs.js +++ /dev/null @@ -1,14 +0,0 @@ -export default function getErrorStrs(errors, processErrorMessage) { - if (errors) { - return errors.map(e => { - const message = e.message || e; - - if (typeof processErrorMessage === 'function') { - return processErrorMessage(message); - } - - return message; - }); - } - return errors; -} diff --git a/src/utils/getIn.js b/src/utils/getIn.js deleted file mode 100644 index 95dd7cf..0000000 --- a/src/utils/getIn.js +++ /dev/null @@ -1,21 +0,0 @@ -export default function getIn(state, name) { - if (!state) { - return state; - } - - const path = name - .replace(/\[/, '.') - .replace(/\]/, '') - .split('.'); - const length = path.length; - if (!length) { - return undefined; - } - - let result = state; - for (let i = 0; i < length && !!result; ++i) { - result = result[path[i]]; - } - - return result; -} diff --git a/src/utils/getParams.js b/src/utils/getParams.js deleted file mode 100644 index aef3a07..0000000 --- a/src/utils/getParams.js +++ /dev/null @@ -1,12 +0,0 @@ -export default function getParams(ns, cb) { - let names = typeof ns === 'string' ? [ns] : ns; - let callback = cb; - if (cb === undefined && typeof names === 'function') { - callback = names; - names = undefined; - } - return { - names, - callback, - }; -} diff --git a/src/utils/getValueFromEvent.js b/src/utils/getValueFromEvent.js deleted file mode 100644 index c0d921f..0000000 --- a/src/utils/getValueFromEvent.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * 从组件事件中获取数据 - * @param e Event或者value - * @returns value - */ -export default function getValueFromEvent(e) { - // support custom element - if (!e || !e.target) { - return e; - } - const { target } = e; - - if (target.type === 'checkbox') { - return target.checked; - } else if (target.type === 'radio') { - //兼容原生radioGroup - if (target.value) { - return target.value; - } else { - return target.checked; - } - } - return target.value; -} diff --git a/src/utils/index.js b/src/utils/index.js deleted file mode 100644 index 32a5c18..0000000 --- a/src/utils/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import _deleteIn from './deleteIn'; -import * as _env from './env'; -import * as _func from './func'; -import _getErrorStrs from './getErrorStrs'; -import _getIn from './getIn'; -import _getParams from './getParams'; -import _getValueFromEvent from './getValueFromEvent'; -import * as _log from './log'; -import _mapValidateRules from './mapValidateRules'; -import * as _object from './object'; -import _setIn from './setIn'; - -export const env = _env; -export const func = _func; -export const log = _log; -export const obj = _object; -export const deleteIn = _deleteIn; -export const getErrorStrs = _getErrorStrs; -export const getIn = _getIn; -export const getParams = _getParams; -export const getValueFromEvent = _getValueFromEvent; -export const mapValidateRules = _mapValidateRules; -export const setIn = _setIn; diff --git a/src/utils/log.js b/src/utils/log.js deleted file mode 100644 index f6bd154..0000000 --- a/src/utils/log.js +++ /dev/null @@ -1,35 +0,0 @@ -import { isProduction } from './env'; - -/* eslint no-console: 0 */ - -/** - * 反对使用某一方法或属性的警告 - * @param {String} props 过时的属性或方法名 - * @param {String} instead 替代的属性或方法名 - * @param {String} component 组件名 - * - * @example - * log.deprecated('onBeforeClose', 'beforeClose', 'Dialog'); - * // Warning: onBeforeClose is deprecated at [ Dialog ], use [ beforeClose ] instead of it. - */ -export function deprecated(props, instead, component) { - /* istanbul ignore else */ - if (!isProduction() && typeof console !== 'undefined' && console.error) { - return console.error( - `Warning: [ ${props} ] is deprecated at [ ${component} ], ` + - `use [ ${instead} ] instead of it.` - ); - } -} - -/** - * 控制台警告日志 - * @param {String} msg - * @return {Console | void} - */ -export function warning(msg) { - /* istanbul ignore else */ - if (!isProduction() && typeof console !== 'undefined' && console.error) { - return console.error(`Warning: ${msg}`); - } -} diff --git a/src/utils/mapValidateRules.js b/src/utils/mapValidateRules.js deleted file mode 100644 index 7358567..0000000 --- a/src/utils/mapValidateRules.js +++ /dev/null @@ -1,39 +0,0 @@ -function validateMap(rulesMap, rule, defaultTrigger) { - const nrule = Object.assign({}, rule); - - if (!nrule.trigger) { - nrule.trigger = [defaultTrigger]; - } - - if (typeof nrule.trigger === 'string') { - nrule.trigger = [nrule.trigger]; - } - - for (let i = 0; i < nrule.trigger.length; i++) { - const trigger = nrule.trigger[i]; - - if (trigger in rulesMap) { - rulesMap[trigger].push(nrule); - } else { - rulesMap[trigger] = [nrule]; - } - } - - delete nrule.trigger; -} - -/** - * 提取rule里面的trigger并且做映射 - * @param {Array} rules 规则 - * @param {String} defaultTrigger 默认触发 - * @return {Object} {onChange:rule1, onBlur: rule2} - */ -export default function mapValidateRules(rules, defaultTrigger) { - const rulesMap = {}; - - rules.forEach(rule => { - validateMap(rulesMap, rule, defaultTrigger); - }); - - return rulesMap; -} diff --git a/src/utils/object.js b/src/utils/object.js deleted file mode 100644 index 3620489..0000000 --- a/src/utils/object.js +++ /dev/null @@ -1,266 +0,0 @@ -/** - * 获取对象的类型 - * @param {*} obj - * @return {String} - * - * @example - * typeOf([]) === 'Array' - * typeOf() === 'Undefined' - * typeOf(1) === 'Number' - */ -export function typeOf(obj) { - return Object.prototype.toString.call(obj).replace(/\[object\s|]/g, ''); -} - -/** - * 判断是否是数组或类数组对象 - * @param {*} obj - * @return {Boolean} - * - * @example - * isArrayLike([]) === true - * isArrayLike(arguments) === true - * isArrayLike(this.props.children) === true - */ -export function isArrayLike(obj) { - const length = !!obj && 'length' in obj && obj.length; - const type = typeOf(obj); - - return ( - type === 'Array' || - length === 0 || - (typeof length === 'number' && length > 0 && length - 1 in obj) - ); -} - -/** - * 判断对象是否是一个promise,即是否可以用.then - * @param {*} obj - * @return {Boolean} - */ -export function isPromise(obj) { - return ( - !!obj && - (typeof obj === 'object' || typeof obj === 'function') && - typeof obj.then === 'function' - ); -} - -/** - * 是否是一个纯净的对象 - * @param {*} obj - * @return {Boolean} - * @reference https://github.com/jonschlinkert/is-plain-object - */ -export function isPlainObject(obj) { - if (typeOf(obj) !== 'Object') { - return false; - } - - const ctor = obj.constructor; - - if (typeof ctor !== 'function') { - return false; - } - - const prot = ctor.prototype; - - if (typeOf(prot) !== 'Object') { - return false; - } - - if (!prot.hasOwnProperty('isPrototypeOf')) { - return false; - } - - return true; -} - -/** - * 对象浅比较 - * @param {Object} objA - * @param {Object} objB - * @param {Function} [compare] 手动调用方法比较 - * @return {Boolean} 对象浅比较是否相等 - * - * @example - * object.shallowEqual({a: 100}, {a: 100}); // true - */ -export function shallowEqual(objA, objB, compare) { - if (objA === objB) { - return true; - } - - // 其中一个不是object,则不相等 - if (!objA || !objB || typeof objA + typeof objB !== 'objectobject') { - return false; - } - - const keyA = Object.keys(objA); - const keyB = Object.keys(objB); - const len = keyA.length; - - // key 数量不一致则不相等 - if (len !== keyB.length) { - return false; - } - - const hasCallback = typeof compare === 'function'; - - for (let i = 0; i < len; i++) { - const key = keyA[i]; - - if (!Object.prototype.hasOwnProperty.call(objB, key)) { - return false; - } - - const valA = objA[key]; - const valB = objB[key]; - - const ret = hasCallback ? compare(valA, valB, key) : void 0; - - if (ret === false || (ret === void 0 && valA !== valB)) { - return false; - } - } - - return true; -} - -/** - * 遍历对象或数组,或者类数组,例如React中的children对象、arguments等 - * @param {Object|Array} obj - * @param {Function} callback fn(n, i) or fn(val, key) - * @param {Number} [direction = 1] 是否倒序遍历,只对数组有效 - * @return {Object|Array} - * - * @example - * // 遍历数组 - * object.each([100, 200, 300], (n, i) => console.log(n, i)); - * // 遍历json对象 - * object.each({a: 100, b: 200}, (value, key) => console.log(key, value)); - * // 遍历React子节点 - * object.each(this.props.children, (child, index) => console.log(child)); - * // 遍历arguments - * object.each(arguments, (arg, i) => console.log(arg)); - */ -export function each(obj, callback, direction) { - const reversed = direction === -1; - const length = obj.length; - let value, - i = reversed ? length - 1 : 0; - - if (isArrayLike(obj)) { - for (; i < length && i >= 0; reversed ? i-- : i++) { - value = callback.call(obj[i], obj[i], i); - - if (value === false) { - break; - } - } - } else { - for (i in obj) { - /* istanbul ignore else */ - if (obj.hasOwnProperty(i)) { - value = callback.call(obj[i], obj[i], i); - - if (value === false) { - break; - } - } - } - } - - return obj; -} - -// @private 判断key是否在数组或对象中 -const _isInObj = (key, obj, isArray) => - isArray ? obj.indexOf(key) > -1 : key in obj; - -/** - * 过滤出其它属性 - * @param {Object|Array} holdProps 过滤的参照对象,最终的结果只保留不在参照对象中的key - * @param {Object} props 被过滤的对象 - * @return {Object} others - * - * @example - * object.pickOthers(FooComponent.propTypes, this.props); - * object.pickOthers(['className', 'onChange'], this.props); - */ -export function pickOthers(holdProps, props) { - const others = {}; - const isArray = typeOf(holdProps) === 'Array'; - - for (const key in props) { - if (!_isInObj(key, holdProps, isArray)) { - others[key] = props[key]; - } - } - - return others; -} - -/** - * 过滤出带prefix的属性 - * @param {Object} holdProps 过滤的参照对象,最终的结果只保留不在参照对象中的key - * @param {string} prefix 包含的字符串 - * @return {Object} others - * - * @example - * object.pickAttrsWith(FooComponent.propTypes, 'data-'); - */ -export function pickAttrsWith(holdProps, prefix) { - const others = {}; - - for (const key in holdProps) { - if (key.match(prefix)) { - others[key] = holdProps[key]; - } - } - - return others; -} - -/** - * Checks if value is `null` or `undefined`. - * @param {*} value - * @return {Boolean} - */ -export function isNil(value) { - // It will returns `true` only if `null` or `undefined` compare with `null` - // with loose equaliy - return value == null; // eslint-disable-line eqeqeq -} - -/** - * Deep merge two objects. - * @param target - * @param ...sources - * @reference https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge?page=1&tab=votes#tab-top - */ -export function deepMerge(target, ...sources) { - if (!sources.length) return target; - const source = sources.shift(); - - if (!isPlainObject(target)) { - target = {}; - } - - if (isPlainObject(target) && isPlainObject(source)) { - for (const key in source) { - if (isPlainObject(source[key])) { - if (!target[key]) Object.assign(target, { [key]: {} }); - // fix {a: 'te'}, {a:{b:3}} - if (!isPlainObject(target[key])) { - target[key] = source[key]; - } - deepMerge(target[key], source[key]); - } else { - Object.assign(target, { [key]: source[key] }); - } - } - } - - return deepMerge(target, ...sources); -} diff --git a/src/utils/setIn.js b/src/utils/setIn.js deleted file mode 100644 index da843a9..0000000 --- a/src/utils/setIn.js +++ /dev/null @@ -1,41 +0,0 @@ -const setInWithPath = (state, value, path, pathIndex) => { - if (pathIndex >= path.length) { - return value; - } - - const first = path[pathIndex]; - const next = setInWithPath( - state && state[first], - value, - path, - pathIndex + 1 - ); - - if (!state) { - const initialized = isNaN(first) ? {} : []; - initialized[first] = next; - return initialized; - } - - if (Array.isArray(state)) { - const copy = [].concat(state); - copy[first] = next; - return copy; - } - - return Object.assign({}, state, { - [first]: next, - }); -}; - -export default function setIn(state, name, value) { - return setInWithPath( - state, - value, - name - .replace(/\[/, '.') - .replace(/\]/, '') - .split('.'), - 0 - ); -}