引用
¥References
引用,或简称为 refs,是稳定的本地值,它们在组件渲染时会持续存在,但不会像状态或 props 那样在更改时触发重新渲染。
¥References, or refs for short, are stable, local values that persist across component renders but don't trigger rerenders like state or props would when they change.
大多数情况下,你会看到 refs 用于促进对 DOM 的命令式操作,但它们可用于存储你需要保持稳定的任意本地值。你可以使用它们来跟踪以前的状态值,保留对间隔或超时 ID 的引用,或者只是一个计数器值。重要的是,ref 不应用于渲染逻辑,而应仅在生命周期方法和事件处理程序中使用。
¥Most often you'll see refs used to facilitate imperative manipulation of the DOM but they can be used to store any arbitrary local value that you need to be kept stable. You may use them to track a previous state value, keep a reference to an interval or timeout ID, or simply a counter value. Importantly, refs should not be used for rendering logic, instead, consumed in lifecycle methods and event handlers only.
创建 Ref
¥Creating a Ref
有两种在 Preact 中创建 refs 的方法,具体取决于你喜欢的组件样式:createRef
(类组件)和 useRef
(函数组件/钩子)。这两个 API 基本上以相同的方式工作:它们创建一个具有 current
属性的稳定、普通对象,可以选择将其初始化为一个值。
¥There are two ways to create refs in Preact, depending on your preferred component style: createRef
(class components) and useRef
(function components/hooks). Both APIs fundamentally work the same way: they create a stable, plain object with a current
property, optionally initialized to a value.
import { createRef } from "preact";
class MyComponent extends Component {
countRef = createRef();
inputRef = createRef(null);
// ...
}
import { useRef } from "preact/hooks";
function MyComponent() {
const countRef = useRef();
const inputRef = useRef(null);
// ...
}
使用 Refs 访问 DOM 节点
¥Using Refs to Access DOM Nodes
refs 最常见的用例是访问组件的底层 DOM 节点。这对于命令式 DOM 操作很有用,例如测量元素、在各种元素上调用原生方法(例如 .focus()
或 .play()
)以及与用 vanilla JS 编写的第三方库集成。在下面的示例中,在渲染时,Preact 会将 DOM 节点分配给 ref 对象的 current
属性,使其在组件安装后可供使用。
¥The most common use case for refs is to access the underlying DOM node of a component. This is useful for imperative DOM manipulation, such as measuring elements, calling native methods on various elements (such as .focus()
or .play()
), and integrating with third-party libraries written in vanilla JS. In the following examples, upon rendering, Preact will assign the DOM node to the current
property of the ref object, making it available for use after the component has mounted.
class MyInput extends Component {
ref = createRef(null);
componentDidMount() {
console.log(this.ref.current);
// Logs: [HTMLInputElement]
}
render() {
return <input ref={this.ref} />;
}
}
Run in REPL
function MyInput() {
const ref = useRef(null);
useEffect(() => {
console.log(ref.current);
// Logs: [HTMLInputElement]
}, []);
return <input ref={ref} />;
}
Run in REPL
回调参考
¥Callback Refs
使用引用的另一种方法是将函数传递给 ref
prop,其中 DOM 节点将作为参数传递。
¥Another way to use references is by passing a function to the ref
prop, where the DOM node will be passed as an argument.
class MyInput extends Component {
render() {
return (
<input ref={(dom) => {
console.log('Mounted:', dom);
// As of Preact 10.23.0, you can optionally return a cleanup function
return () => {
console.log('Unmounted:', dom);
};
}} />
);
}
}
Run in REPL
function MyInput() {
return (
<input ref={(dom) => {
console.log('Mounted:', dom);
// As of Preact 10.23.0, you can optionally return a cleanup function
return () => {
console.log('Unmounted:', dom);
};
}} />
);
}
Run in REPL
如果提供的 ref 回调不稳定(例如如上所示内联定义的回调),并且不返回清理函数,则它将在所有重新渲染时被调用两次:一次使用
null
,然后一次使用实际引用。这是一个常见问题,createRef
/useRef
API 通过强制用户检查ref.current
是否已定义使此问题变得更容易一些。¥If the provided ref callback is unstable (such as one that's defined inline, as shown above), and does not return a cleanup function, it will be called twice upon all rerenders: once with
null
and then once with the actual reference. This is a common issue and thecreateRef
/useRef
APIs make this a little easier by forcing the user to check ifref.current
is defined.例如,稳定函数可以是类组件实例上的方法、在组件外部定义的函数或使用
useCallback
创建的函数。¥A stable function, for comparison, could be a method on the class component instance, a function defined outside of the component, or a function created with
useCallback
, for example.
使用 Refs 存储本地值
¥Using Refs to Store Local Values
但是,Refs 并不局限于存储 DOM 节点;它们可用于存储你可能需要的任何类型的值。
¥Refs aren't limited to storing DOM nodes, however; they can be used to store any type of value that you may need.
在下面的示例中,我们将间隔的 ID 存储在 ref 中,以便能够独立启动和停止它。
¥In the following example, we store the ID of an interval in a ref to be able to start & stop it independently.
class SimpleClock extends Component {
state = {
time: Date.now(),
};
intervalId = createRef(null);
startClock = () => {
this.setState({ time: Date.now() });
this.intervalId.current = setInterval(() => {
this.setState({ time: Date.now() });
}, 1000);
};
stopClock = () => {
clearInterval(this.intervalId.current);
};
render(_, { time }) {
const formattedTime = new Date(time).toLocaleTimeString();
return (
<div>
<button onClick={this.startClock}>Start Clock</button>
<time dateTime={formattedTime}>{formattedTime}</time>
<button onClick={this.stopClock}>Stop Clock</button>
</div>
);
}
}
Run in REPL
function SimpleClock() {
const [time, setTime] = useState(Date.now());
const intervalId = useRef(null);
const startClock = () => {
setTime(Date.now());
intervalId.current = setInterval(() => {
setTime(Date.now());
}, 1000);
};
const stopClock = () => {
clearInterval(intervalId.current);
};
const formattedTime = new Date(time).toLocaleTimeString();
return (
<div>
<button onClick={startClock}>Start Clock</button>
<time dateTime={formattedTime}>{formattedTime}</time>
<button onClick={stopClock}>Stop Clock</button>
</div>
);
}
Run in REPL