React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React  onChange 事件

React 表单控件 onChange事件插件的使用

作者:YFF菲菲兔

本文主要介绍了React 表单控件 onChange事件插件的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

表单专属事件插件,整合各类表单受控组件原生输入 / 变更事件,抹平浏览器输入行为与输入法差异,提供统一、稳定的 onChange 合成事件,支撑 input/textarea/radio/checkbox/select/file 全类型受控表单开发

注册事件:registerEvents

function registerEvents() {
  registerTwoPhaseEvent('onChange', [
    'change',
    'click',
    'focusin',
    'focusout',
    'input',
    'keydown',
    'keyup',
    'selectionchange',
  ]);
}

提取事件:extractEvents

根据当前表单控件类型,匹配对应处理工具,找到真正触发变更的 Fiber,生成并收集 onChange 合成事件推入调度队列;同时兼容旧浏览器 input 事件垫片、受控组件失焦特殊逻辑

function extractEvents(
  dispatchQueue,
  domEventName,
  targetInst,
  nativeEvent,
  nativeEventTarget,
  eventSystemFlags,
  targetContainer,
) {
  const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;

  let getTargetInstFunc, handleEventFunc;
  if (shouldUseChangeEvent(targetNode)) {
    getTargetInstFunc = getTargetInstForChangeEvent;
  } else if (isTextInputElement(targetNode) {
    if (isInputEventSupported) {
      getTargetInstFunc = getTargetInstForInputOrChangeEvent;
    } else {
      getTargetInstFunc = getTargetInstForInputEventPolyfill;
      handleEventFunc = handleEventsForInputEventPolyfill;
    }
  } else if (shouldUseClickEvent(targetNode)) {
    getTargetInstFunc = getTargetInstForClickEvent;
  } else if (
    targetInst &&
    isCustomElement(targetInst.elementType, targetInst.memoizedProps)
  ) {
    getTargetInstFunc = getTargetInstForChangeEvent;
  }

  if (getTargetInstFunc) {
    const inst = getTargetInstFunc(domEventName, targetInst);
    if (inst) {
      createAndAccumulateChangeEvent(
        dispatchQueue,
        inst,
        nativeEvent,
        nativeEventTarget,
      );
      return;
    }
  }

  if (handleEventFunc) {
    handleEventFunc(domEventName, targetNode, targetInst);
  }

  if (domEventName === 'focusout' && targetInst) {

    const props = targetInst.memoizedProps;
    handleControlledInputBlur(targetNode), props);
  }
}

辅助函数

获取 Fiber 对应的 DOM:getNodeFromInstance

输入合法 Host 类 Fiber,安全取出对应的真实 DOM 节点

export function getNodeFromInstance(inst) {
  const tag = inst.tag;
  if (
    // 普通DOM元素 <div> <input>
    tag === HostComponent ||
    // 可提升的宿主DOM(如title、meta等head内标签)
    tag === HostHoistable ||
    // 单例宿主(html、body这类全局唯一DOM)
    tag === HostSingleton ||
    // 文本节点 Text
    tag === HostText
  ) {
    return inst.stateNode;
  }
  throw new Error('getNodeFromInstance: Invalid argument.');
}

区分仅靠 change 代表变更的表单控件:shouldUseChangeEvent

判断当前 DOM 是否为 select 下拉框 / file 文件上传输入框

function shouldUseChangeEvent(elem) {
  const nodeName = elem.nodeName && elem.nodeName.toLowerCase();
  return (
    nodeName === 'select' ||
    (nodeName === 'input' && (elem: any).type === 'file')
  );
}

获取 change 事件的目标实例:getTargetInstForChangeEvent

仅当原生事件名是 change 时,返回当前触发事件的 Fiber targetInst

function getTargetInstForChangeEvent(
  domEventName,
  targetInst,
) {
  if (domEventName === 'change') {
    return targetInst;
  }
}

判断文本输入控件:isTextInputElement

区分实时文本输入型控件

const supportedInputTypes = {
  color: true,
  date: true,
  datetime: true,
  'datetime-local': true,
  email: true,
  month: true,
  number: true,
  password: true,
  range: true,
  search: true,
  tel: true,
  text: true,
  time: true,
  url: true,
  week: true,
};

function isTextInputElement(elem) {
  const nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();

  if (nodeName === 'input') {
    return !!supportedInputTypes[elem.type];
  }

  if (nodeName === 'textarea') {
    return true;
  }

  return false;
}

获取文本输入事件的目标实例:getTargetInstForInputOrChangeEvent

文本输入控件专用事件过滤器:仅给 <input type=text/email/password>、<textarea> 使用,只放行 input/change 两类有效原生事件

function getTargetInstForInputOrChangeEvent(
  domEventName,
  targetInst,
) {
  if (domEventName === 'input' || domEventName === 'change') {
    return getInstIfValueChanged(targetInst);
  }
}

模拟 input 实时输入能力:getTargetInstForInputEventPolyfill

模拟现代浏览器 input 实时输入能力,在无原生 input 事件的浏览器上,靠键盘 / 选区事件兜底生成 onChange,保证文本输入实时触发变更事件

function getTargetInstForInputEventPolyfill(
  domEventName,
  targetInst,
) {
  if (
    domEventName === 'selectionchange' ||
    domEventName === 'keyup' ||
    domEventName === 'keydown'
  ) {
    return getInstIfValueChanged(activeElementInst);
  }
}

获取表单控件值变更的实例:getInstIfValueChanged

过滤输入无改动的无效交互,避免重复触发 onChange,降低高频打字、光标选区操作的性能开销;统一为文本输入、垫片逻辑提供值变更判断复用能力

function getInstIfValueChanged(targetInst) {
  const targetNode = getNodeFromInstance(targetInst);
  if (updateValueIfChanged(targetNode) {
    return targetInst;
  }
}

表单控件值变更检测:updateValueIfChanged

为输入框 DOM 绑定专属值追踪器 tracker,对比缓存旧值与当前 DOM 真实值,判断内容是否发生变化

export function updateValueIfChanged(node) {
  if (!node) {
    return false;
  }

  const tracker = getTracker(node);
  if (!tracker) {
    return true;
  }

  const lastValue = tracker.getValue();
  const nextValue = getValueFromNode(node);
  if (nextValue !== lastValue) {
    tracker.setValue(nextValue);
    return true;
  }
  return false;
}

提取各类表单控件的值:getValueFromNode

统一提取各类表单 DOM 的状态字符串,区分可勾选控件(checkbox/radio)与普通输入控件,输出标准化字符串格式的值

function getValueFromNode(node) {
  let value = '';
  if (!node) {
    return value;
  }

  if (isCheckable(node)) {
    value = node.checked ? 'true' : 'false';
  } else {
    value = node.value;
  }

  return value;
}

识别单选 / 复选框表单控件:isCheckable

先校验标签名是 input,再匹配 type 为 checkbox /radio,兼容 DOM 标签大小写

function isCheckable(elem) {
  const type = elem.type;
  const nodeName = elem.nodeName;
  return (
    nodeName &&
    nodeName.toLowerCase() === 'input' &&
    (type === 'checkbox' || type === 'radio')
  );
}

文本输入垫片的焦点生命周期管理器:handleEventsForInputEventPolyfill

不支持标准 input 事件的老旧浏览器(IE)文本输入垫片,绑定输入框焦点与监听开关,只在输入框激活时跟踪值变化,失焦后释放监听,保证老旧浏览器依靠键盘、选区事件模拟实时 onChange 时数据准确、多输入框互不干扰

function handleEventsForInputEventPolyfill(
  domEventName,
  target,
  targetInst,
) {
  if (domEventName === 'focusin') {
    stopWatchingForValueChange();
    startWatchingForValueChange(target, targetInst);
  } else if (domEventName === 'focusout') {
    stopWatchingForValueChange();
  }
}

开启实时值变更监听:startWatchingForValueChange

为旧浏览器文本输入框开启实时值变更监听,替代缺失的标准 input 事件,绑定 IE 专属 propertychange DOM 事件,监听 value 属性修改,捕获打字、粘贴等内容变动

function startWatchingForValueChange(
  target,
  targetInst,
) {
  activeElement = target;
  activeElementInst = targetInst;
  activeElement.attachEvent('onpropertychange', handlePropertyChange);
}

关闭实时值变更监听:stopWatchingForValueChange

停止对当前激活输入框的实时值监听,解绑 IE 专属 onpropertychange 事件,释放 DOM 监听避免内存泄漏,清空全局缓存变量 activeElement、activeElementInst,取消当前输入框激活标记

function stopWatchingForValueChange() {
  if (!activeElement) {
    return;
  }
  activeElement.detachEvent('onpropertychange', handlePropertyChange);
  activeElement = null;
  activeElementInst = null;
}

文本类表单控件值变化监听函数:handlePropertyChange

仅在老旧浏览器文本框激活阶段生效,模拟现代浏览器 input 事件行为,统一 React onChange 跨浏览器表现。过滤 value 属性变动,其余属性修改直接跳过,值变更时手动派发 onChange 合成事件,补齐旧浏览器无原生 input 事件的短板,实现文本输入实时触发变更回调

function handlePropertyChange(nativeEvent) {
  if (nativeEvent.propertyName !== 'value') {
    return;
  }
  if (getInstIfValueChanged(activeElementInst)) {
    manualDispatchChangeEvent(nativeEvent);
  }
}

手动派发事件:manualDispatchChangeEvent

垫片捕获到输入框 value 变化后调用,主动生成 onChange 合成事件。新建临时调度队列,调用createAndAccumulateChangeEvent构造 SyntheticChangeEvent、收集对应 onChange 回调存入队列,批量更新调度,批量执行队列内所有事件回调,统一走 React 批量更新机制,避免频繁渲染卡顿。补齐老旧浏览器缺失的标准 input 事件能力,让 IE 文本框也能实时触发 onChange,和现代浏览器行为保持一致

function manualDispatchChangeEvent(nativeEvent) {
  const dispatchQueue = [];
  createAndAccumulateChangeEvent(
    dispatchQueue,
    activeElementInst,
    nativeEvent,
    getEventTarget(nativeEvent),
  );

  batchedUpdates(runEventInBatch, dispatchQueue);
}

识别单选/复选框表单控件:shouldUseClickEvent

识别 <input type="checkbox">、<input type="radio"> 单选 / 复选框

function shouldUseClickEvent(elem) {
  const nodeName = elem.nodeName;
  return (
    nodeName &&
    nodeName.toLowerCase() === 'input' &&
    (elem.type === 'checkbox' || elem.type === 'radio')
  );
}

获取 click 事件的目标实例:getTargetInstForClickEvent

专供 checkbox/radio 勾选框使用的事件过滤工具,仅放行原生 click 事件,屏蔽键盘、焦点等无关渠道,返回有效 Fiber

function getTargetInstForClickEvent(
  domEventName,
  targetInst,
) {
  if (domEventName === 'click') {
    return getInstIfValueChanged(targetInst);
  }
}

识别 Web 自定义元素:isCustomElement

function isCustomElement(tagName, props) {
  if (tagName.indexOf('-') === -1) {
    return false;
  }
  switch (tagName) {
    case 'annotation-xml':
    case 'color-profile':
    case 'font-face':
    case 'font-face-src':
    case 'font-face-uri':
    case 'font-face-format':
    case 'font-face-name':
    case 'missing-glyph':
      return false;
    default:
      return true;
  }
}

Change 事件生成与收集:createAndAccumulateChangeEvent

function createAndAccumulateChangeEvent(
  dispatchQueue,
  inst,
  nativeEvent,
  target,
) {
  enqueueStateRestore(target);
  const listeners = accumulateTwoPhaseListeners(inst, 'onChange');
  if (listeners.length > 0) {
    const event = new SyntheticEvent(
      'onChange',
      'change',
      null,
      nativeEvent,
      target,
    );
    dispatchQueue.push({event, listeners});
  }
}

受控输入框值恢复任务入队:enqueueStateRestore

缓存需要矫正的输入 DOM,供 focusout 失焦时统一同步 state 与 DOM 值,支持多输入框批量矫正,配合handleControlledInputBlur 在输入框失焦时取出队列 DOM 做值修复,解决受控组件光标错位、值不同步的经典问题,统一管理受控输入框矫正任务,避免多次重复矫正,保证表单双向绑定稳定

export function enqueueStateRestore(target) {
  if (restoreTarget) {
    if (restoreQueue) {
      restoreQueue.push(target);
    } else {
      restoreQueue = [target];
    }
  } else {
    restoreTarget = target;
  }
}

受控输入框失焦处理:handleControlledInputBlur

数字受控输入框失焦专属兼容修复函数,仅处理 number 类型输入框,解决数字输入框原生行为缺陷,即用户输入非法数字(如字母、超出范围值)后,DOM value 会自动清空,导致受控组件状态与 DOM 脱节。失焦时调用 setDefaultValue 将当前 DOM 输入值写入节点默认值属性,维持受控组件 value 同步,避免页面渲染闪烁、数值丢失

function handleControlledInputBlur(node, props) {
  if (node.type !== 'number') {
    return;
  }

  if (!disableInputAttributeSyncing) {
    const isControlled = props.value != null;
    if (isControlled) {
      setDefaultValue(node, 'number', node.value);
    }
  }
}

更新受控输入框默认值:setDefaultValue

仅为 number 类型受控输入框更新 defaultValue,过滤其他输入类型;同时仅在输入框非当前激活态时执行,避免编辑中频繁改动默认值干扰输入。统一将传入值转为字符串,对比节点原有 defaultValue,不一致才更新,减少 DOM 属性重复修改

export function setDefaultValue(
  node,
  type,
  value,
) {
  if (
    type !== 'number' ||
    getActiveElement(node.ownerDocument) !== node
  ) {
    if (node.defaultValue !== toString(value)) {
      node.defaultValue = toString(value);
    }
  }
}

获取当前聚焦激活 DOM 元素:getActiveElement

获取文档当前聚焦激活 DOM 元素,自动兜底全局document,无文档环境返回null

export default function getActiveElement(doc) {
  doc = doc || (typeof document !== 'undefined' ? document : undefined);
  if (typeof doc === 'undefined') {
    return null;
  }
  try {
    return doc.activeElement || doc.body;
  } catch (e) {
    return doc.body;
  }
}

到此这篇关于React 表单控件 onChange事件插件的使用的文章就介绍到这了,更多相关React onChange 事件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文