React关于UseRef

发布时间 2023-06-27 14:50:10作者: 脏猫

useRef简单介绍

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。

一个常见的用例便是命令式地访问子组件:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。

你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以

形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。

然而,useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

useRef的特性

useRef可以存储任意类型的值

const initialValue = 1;
const ref = useRef(initialValue); // 初始化
const refList = useRef([]);
const refObj = useRef({});
console.log(ref.current); // 1
ref.current = 2; // 更新ref值

useRef方法返回的对象,且这个对象在组件生命周期中保持不变

const ref = useRef(1);
useEffect(() => {
  ref.current++;
  console.log(ref.current); // 2
}, [ref])

样例中的代码并不会发出死循环,因为ref.current更新后,ref的引用还是没有变

更新current的值不会触发组件重新渲染

const ref = useRef(1);

return <div><span>{ref.current}</span><a onClick={() => ref.current++}>+</a></div>

样例中不断点击a标签触发current更新,但是页面上任然显示1

useRef的用法

存储不会影响页面渲染的值

获取dom对象

const ref = useRef();
useEffect(() => {   
  console.log(ref.current.innerText); // 输出结果为 123
}, [ref])

return <div ref={ref}>123</div>

将ref作为参数传入到HTMLElement中就可以获取它的dom对象.

注: HTMLElement*指的是html*原生的对象元素,例如 *div* *span*

在闭包函数中获取最新的值

const ref = useRef(1);
const [state, setState] = useState(1);

// useEffect中嵌套两个箭头函数,表示组件被销毁后执行
useEffect(() => () => {
    setTimeout(() => {
        console.log(ref.current, state); // 假定在3s内点击3次,输出结果为 4 1
    }, 3000)
  // 无论触发了多少次onClick,打印的state值永远是1,而ref.current值显示正常
}, [])

return <a onClick={() => { ref.current++;setState(state + 1); }}>+</a>

与useImperativeHandle组合使用,父组件可以直接获取子组件的方法

import { useImperativeHandle, forwardRef, useEffect, useRef } from 'react';

const Child = forwardRef((props, ref) => {
  const childFC = () => {
    console.log('Child');
  };

  useImperativeHandle(ref, () => ({
    childFC: childFC,
  }));

  return <div>子组件</div>;
});

const Parent = () => {
  const ref = useRef();

  useEffect(() => {
    ref.current.childFC(); // 打印结果为 Child
  }, []);

  return <Child ref={ref}>child</Child>;
};

与useEffect组合使用,可以产生类似Class组件的componentDidUpdate的效果

const ref = useRef(false);
// 监听变量a
useEffect(() => {
  if (!ref.current) {
     ref.current = true;
     return;
  }
  // 变量a更新后的逻辑
  ...
}, [a]);

注:这种写法可能会导致代码逻辑混乱,非必要不使用. 应该优先在触发a 更新的event handler方法中处理a的更新逻辑

注意事项

useRef 和其他的hook一样,必须创建在组件的在外层

// 正确使用
const App = () => {
  const ref = useRef();
}

// 错误使用
const App = () => {
  const init = () => {
    const ref = useRef();
  }
}

// 错误使用
const App = () => {
  useEffect(() => {
    const ref = useRef();
  }, [])
}

更新useRef是副作用,不能在主函数,而应该放在useEffect或 event handler等可以处理副作用的方法中

// 正确使用
const App = () => {
  const [state, setState] = useState(1);
  const ref = useRef(1);

  useEffect(() => () => {
    ref.current++;
  }, [])

  return <a onClick={() => { ref.current++;setState(state + 1); }}>+</a>
}

// 错误使用
const App = () => {  
  const [state, setState] = useState(1);
  const ref = useRef(1);
  ref.current = ref.current + 1;

  return <a onClick={() => { setState(state + 1); }}>+</a>
}

关于forwardRef

由于ref不能被直接分配给函数式组件,所以我们需要用forwardRef方法包裹函数式组件,使其能够接收到ref_

const App = forwardRef((props, ref) => {

  return <div ref={ref}>app</div>
})

createRef和useRef的区别

useRef 仅能用在 FunctionComponent,因为 Hooks 不能用在 ClassComponent.

createRef 仅能用在 ClassComponent.因为createRef 并没有 Hooks 的效果,其值会随着 FunctionComponent 重复执行而不断被初始化:

const App = () => {
  // 错误用法,永远也拿不到 ref
  const valueRef = React.createRef();
  return <div ref={valueRef} />;
}

// 正确用法
class App extends Component {
    constructor(props) {
        super(props);
        this.ref = React.createRef();
    }
    
    render() {
        return <div ref={this.ref}>app</div>
    }
}