React和vue的理论难题

Fragment的理解

Fragment是React中一个内置组件,允许将多个子元素分组渲染而不添加额外的DOM节点。它类似于一个透明的容器,不会在最终DOM结构中生成实际标签,仅用于逻辑上的包裹。Fragment的主要优势在于避免不必要的div嵌套,保持DOM结构简洁。

核心特性

  • 无真实DOM节点:Fragment不会渲染为实际的HTML元素,仅作为逻辑容器。
  • 简写语法:可以用空标签<>代替,但不支持key属性。
  • 支持key属性:完整写法可用于列表渲染时避免警告。

使用场景

避免额外DOM嵌套
当组件需要返回多个相邻元素但父组件要求单一根节点时,Fragment可以替代div包裹。

function Table() {
  return (
    
      Column 1
      Column 2
    
  );
}

列表渲染优化
在map循环中,Fragment的key属性可以避免元素被包裹在div中。

{items.map(item => (
  
    
  • {item.name}
  • {item.price}
  • ))}

    条件渲染组合
    需要分组渲染条件分支中的多个元素时,Fragment能保持结构清晰。

    {isLoading ? (
      
        
        
      
    ) : (
      
    )}
    

    注意事项

    • 简写语法<>不支持任何属性(如key或children)。
    • 某些CSS方案(如Flexbox/Grid)可能依赖DOM层级,需评估Fragment是否影响布局。
    • 调试工具中Fragment会显示为虚拟节点。

    React.forwardRef 的定义

    React.forwardRef 是 React 提供的高阶组件(HOC),用于将 ref 属性自动向下传递到子组件中的特定 DOM 元素或组件实例。它解决了一个常见问题:在自定义组件中直接使用 ref 时,ref 会绑定到组件实例而非底层 DOM 节点。

    核心作用

    1. 跨组件传递 ref
      允许父组件直接访问子组件内部的 DOM 节点或组件实例,无需通过 props 中转。

    2. 支持底层 DOM 操作
      在封装输入框、模态框等需要直接操作 DOM 的组件时,forwardRef 提供了一种标准化的传递方式。

    3. 兼容第三方库
      许多库(如 react-routermaterial-ui)依赖 forwardRef 实现组件扩展,确保 ref 能正确传递到目标节点。

    使用语法

    const ChildComponent = React.forwardRef((props, ref) => {
      return ;
    });
    
    // 父组件中直接使用 ref
    function ParentComponent() {
      const inputRef = useRef();
      return ;
    }
    

    典型场景示例

    封装可聚焦输入框
    const FancyInput = React.forwardRef((props, ref) => {
      return ;
    });
    
    function App() {
      const inputRef = useRef();
      useEffect(() => {
        inputRef.current.focus(); // 直接操作子组件的 DOM
      }, []);
      return ;
    }
    

    与高阶组件结合

    若组件经过 HOC 包装,forwardRef 能确保 ref 穿透多层包装:

    const EnhancedComponent = React.forwardRef((props, ref) => {
      return ;
    });
    

    注意事项

    • 函数组件限制
      普通函数组件无法接收 ref 参数,必须通过 forwardRef 包裹才能处理 ref
    • 调试名称
      建议设置 displayName 以方便调试:
      ChildComponent.displayName = 'ForwardRef(ChildComponent)';
      

    • 避免滥用
      优先考虑通过 props 控制组件行为,仅在需要直接操作 DOM 或实例时使用。

    生命周期触发差异:state 和 props

    state 变化触发的生命周期

    当组件内部的 state 发生变化时,会触发以下生命周期方法(以类组件为例):

    shouldComponentUpdate(nextProps, nextState) // 判断是否需要更新
    componentWillUpdate(nextProps, nextState)  // 即将更新(旧版 React)
    render()                                   // 重新渲染
    componentDidUpdate(prevProps, prevState)   // 更新完成
    

    props 变化触发的生命周期

    当父组件传递的 props 发生变化时,会触发以下生命周期方法:

    componentWillReceiveProps(nextProps)       // 即将接收新 props(旧版 React)
    shouldComponentUpdate(nextProps, nextState) // 判断是否需要更新
    componentWillUpdate(nextProps, nextState)  // 即将更新(旧版 React)
    render()                                   // 重新渲染
    componentDidUpdate(prevProps, prevState)   // 更新完成
    

    关键区别点
    1. 额外阶段props 变化会多触发 componentWillReceiveProps(或新版 getDerivedStateFromProps),而 state 变化不会。
    2. 更新来源props 更新通常来自父组件,state 更新来自组件自身。
    函数组件中的等效行为

    对于函数组件,propsstate 变化都会导致整个函数重新执行:

    function MyComponent(props) {
      const [state, setState] = useState();
      // 任何 props 或 state 变化都会触发重新执行
      return 
    {props.value} - {state}
    ; }

    现代 React 的替代方案

    在 React 16.3+ 版本中:

    • componentWillReceivePropsgetDerivedStateFromProps 取代
    • componentWillUpdategetSnapshotBeforeUpdate 取代
    static getDerivedStateFromProps(props, state) {
      // 返回新的 state 或 null
    }
    getSnapshotBeforeUpdate(prevProps, prevState) {
      // 返回 snapshot 或 null
    }
    

    自定义指令的实现方法

    在Vue中自定义指令分为全局注册和局部注册两种方式。全局注册通过Vue.directive实现,局部注册在组件选项中通过directives实现。

    全局注册示例:

    Vue.directive('focus', {
      inserted: function(el) {
        el.focus()
      }
    })
    

    局部注册示例:

    export default {
      directives: {
        focus: {
          inserted: function(el) {
            el.focus()
          }
        }
      }
    }
    

    指令的生命周期钩子

    自定义指令提供多个生命周期钩子函数:

    • bind:指令第一次绑定到元素时调用
    • inserted:被绑定元素插入父节点时调用
    • update:所在组件的VNode更新时调用
    • componentUpdated:所在组件及子组件VNode全部更新后调用
    • unbind:指令与元素解绑时调用
    Vue.directive('demo', {
      bind: function(el, binding, vnode) {},
      inserted: function(el, binding, vnode) {},
      update: function(el, binding, vnode, oldVnode) {},
      componentUpdated: function(el, binding, vnode, oldVnode) {},
      unbind: function(el, binding, vnode) {}
    })
    

    指令参数解析

    钩子函数会接收到以下参数:

    • el:指令绑定的DOM元素
    • binding:包含指令信息的对象
    • vnode:Vue编译生成的虚拟节点
    • oldVnode:上一个虚拟节点

    binding对象包含以下属性:

    {
      name: '指令名',
      value: '指令的绑定值',
      oldValue: '指令绑定的前一个值',
      expression: '字符串形式的指令表达式',
      arg: '传给指令的参数',
      modifiers: '包含修饰符的对象'
    }
    

    常见使用场景

    表单自动聚焦

    Vue.directive('focus', {
      inserted: function(el) {
        el.focus()
      }
    })
    

    权限控制

    Vue.directive('permission', {
      inserted: function(el, binding) {
        if(!hasPermission(binding.value)) {
          el.parentNode.removeChild(el)
        }
      }
    })
    

    图片懒加载

    Vue.directive('lazy', {
      inserted: function(el, binding) {
        const observer = new IntersectionObserver((entries) => {
          entries.forEach(entry => {
            if(entry.isIntersecting) {
              el.src = binding.value
              observer.unobserve(el)
            }
          })
        })
        observer.observe(el)
      }
    })
    

    拖拽功能

    Vue.directive('drag', {
      bind: function(el) {
        el.onmousedown = function(e) {
          const disX = e.clientX - el.offsetLeft
          const disY = e.clientY - el.offsetTop
          document.onmousemove = function(e) {
            el.style.left = e.clientX - disX + 'px'
            el.style.top = e.clientY - disY + 'px'
          }
          document.onmouseup = function() {
            document.onmousemove = null
            document.onmouseup = null
          }
        }
      }
    })
    

    动态指令参数

    指令的参数可以是动态的,通过方括号语法实现动态参数:

    Vue.directive('demo', {
      bind: function(el, binding) {
        el.style.position = 'fixed'
        const s = binding.arg === 'left' ? 'left' : 'top'
        el.style[s] = binding.value + 'px'
      }
    })
    

    指令与组件通信

    指令可以通过组件实例访问数据和方法:

    Vue.directive('demo', {
      bind: function(el, binding, vnode) {
        const vm = vnode.context
        vm.$watch(binding.expression, function(value) {
          el.innerHTML = value
        })
      }
    })
    

    vue的$nextTick的作用

    $nextTick是Vue提供的一个异步方法,用于在下次DOM更新循环结束之后执行延迟回调。其主要作用是确保在DOM更新完成后执行某些操作,避免因DOM未及时更新导致的问题。

    this.$nextTick(() => {
      // DOM更新完成后执行的操作
    })
    

    $nextTick的触发时机

    $nextTick会在以下场景触发:

    1. 数据变化后:当Vue实例中的数据发生变化,引发DOM更新后,$nextTick的回调会在DOM更新完成后执行。
    this.message = 'new message'
    this.$nextTick(() => {
      console.log('DOM updated')
    })
    

    1. 生命周期钩子中:在mounted等生命周期钩子中使用时,可以确保回调在初始DOM渲染完成后执行。
    mounted() {
      this.$nextTick(() => {
        console.log('DOM is fully rendered')
      })
    }
    

    1. 组件更新后:当子组件更新完成后,通过$nextTick可以获取更新后的DOM状态。
    this.$refs.childComponent.someMethod()
    this.$nextTick(() => {
      // 子组件更新后的操作
    })
    

    使用场景示例

    • 获取更新后的DOM元素:在修改数据后立即获取DOM元素的属性。
    this.showElement = true
    this.$nextTick(() => {
      const height = this.$refs.element.clientHeight
      console.log(height)
    })
    

    • 依赖DOM的操作:某些操作(如初始化第三方库)需要等待DOM完全渲染。
    this.$nextTick(() => {
      new ThirdPartyLibrary(this.$refs.container)
    })
    

    $nextTick利用了JavaScript的事件循环机制,通常基于微任务(如Promise)实现,确保回调在当前同步任务完成后、下一次DOM更新前执行。

    setState 和 replaceState 的区别

    React 中的 setStatereplaceState 是用于更新组件状态的两种方法,但它们在行为上有显著的不同。

    setState

    setState 是 React 中最常用的状态更新方法。它会将新状态 合并 到当前状态中,而不是完全替换。这意味着未在新状态中指定的属性会保留其当前值。

    this.setState({ key: 'newValue' });
    

    • 合并行为:新状态会和当前状态合并,保留未修改的字段。
    • 异步性setState 是异步的,React 可能会批量处理多个 setState 调用以提高性能。
    • 回调函数:可以传入回调函数作为第二个参数,在状态更新完成后执行。
    this.setState({ count: this.state.count + 1 }, () => {
      console.log('状态已更新');
    });
    

    replaceState

    replaceState 是 React 的旧 API,现已废弃(React 16+ 不再支持)。它的行为与 setState 不同,会 完全替换 当前状态,而不是合并。

    this.replaceState({ newKey: 'newValue' });
    

    • 替换行为:完全用新状态替换当前状态,未指定的字段会被丢弃。
    • 同步性replaceState 是同步的,会立即更新状态。
    • 已废弃:从 React 16 开始,replaceState 被移除,建议使用 setState 或其他状态管理方案。
    关键区别
    1. 合并 vs 替换setState 合并状态,replaceState 完全替换状态。
    2. 异步 vs 同步setState 是异步的,replaceState 是同步的(但已废弃)。
    3. 现代用法replaceState 已废弃,setState 是推荐的方式。

    如果需要在现代 React 中完全替换状态,可以通过 setState 直接传递完整的新状态对象:

    this.setState({ ...newState });
    

    或者使用函数式 setState 确保完全替换:

    this.setState(() => ({ ...newState }));
    

    你可能感兴趣的:(react.js,javascript,前端,vue.js)