在函数组件中存储非状态变量

下面是两个反应组件,做 差不多相同的事情。一个是函数,另一个是类。每个组件都有一个带有异步侦听器的 Animated.Value,该侦听器在更改时更新 _foo。我需要能够在函数组件中访问 _foo,就像我在经典组件中访问 this._foo一样。

  • 如果有多个 FunctionalBar,则 FunctionalBar在全局范围内不应该有 _foo
  • FunctionalBar不能在函数作用域中使用 _foo,因为每次 FunctionalBar呈现时都会重新初始化 _foo_foo也不应该处于状态,因为组件在 _foo更改时不需要呈现。
  • ClassBar没有这个问题,因为它在组件的整个生命周期中始终在 this上初始化 _foo

如何在 FunctionalBar的整个生命周期中保持 _foo的初始化,而不将其放在全局范围内?

实施功能

import React from 'react';
import { Animated, View } from 'react-native';


var _foo = 0;


function FunctionalBar(props) {


const foo = new Animated.Value(0);


_onChangeFoo({ value }) {
_foo = value;
}


function showFoo() {
let anim = Animated.timing(foo, { toValue: 1, duration: 1000, useNativeDriver: true });
anim.start(() => console.log(_foo));
}


useEffect(() => {
foo.addListener(_onChangeFoo);
showFoo();
return () => foo.removeListener(_onChangeFoo);
});


return <View />;


}

经典实现

import React from 'react';
import { Animated, View } from 'react-native';


class ClassBar extends React.Component {


constructor(props) {
super(props);
this.state = { foo: new Animated.Value(0) };
this._foo = 0;
this._onChangeFoo = this._onChangeFoo.bind(this);
}


componentDidMount() {
this.state.foo.addListener(this._onChangeFoo);
this.showFoo();
}


componentWillUnmount() {
this.state.foo.removeListener(this._onChangeFoo);
}


showFoo() {
let anim = Animated.timing(this.state.foo, { toValue: 1, duration: 1000, useNativeDriver: true });
anim.start(() => console.log(this._foo));
}


_onChangeFoo({ value }) {
this._foo = value;
}


render() {
return <View />;
}


}
55625 次浏览

This is a pretty unusual example, but if I'm reading this correctly, you simply want to store unique _foo objects everytime the component mounts and destroy them when it unmounts, but also prevent extra rerenders when this value changes.

I have run into this scenario before and simple object (map / hash) should do the trick:

let foos = {}
let fooCount = 0


function F(props) {
useEffect(() => {
let fooId = fooCount++
foos[fooId] = new Animated.Value(0)
foos[fooId].addListener(...)
return () => foos[fooId].removeListener(...)
}, []) // <-- do not rerun when called again (only when unmounted)


...render...
}

or something to that effect. if you have a runnable example could tweak it to make it fit your example better. either way, most things with scope problems are solved with primitives.

The useRef hook is not just for DOM refs, but can store any mutable value you like.

Example

function FunctionalBar(props) {
const [foo] = useState(new Animated.Value(0));
const _foo = useRef(0);


function showFoo() {
let anim = Animated.timing(foo, { toValue: 1, duration: 1000, useNativeDriver: true });
anim.start(() => console.log(_foo.current));
}


useEffect(() => {
function _onChangeFoo({ value }) {
_foo.current = value;
}


foo.addListener(_onChangeFoo);
showFoo();
return () => foo.removeListener(_onChangeFoo);
}, []);


return <View />;
}

Just to support Tholle answer here is the official documentation

Reference

However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

This works because useRef() creates a plain JavaScript object. The only difference between useRef() and creating a {current: ...} object yourself is that useRef will give you the same ref object on every render.

Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.

You can use useRef hook (it's the recommended way stated in docs):

  • Declaring variable: const a = useRef(5) // 5 is initial value
  • getting the value: a.current
  • setting the value: a.current = my_value

I've had some luck using the useRef hook with destructuring (+ an optional variable alias "my"), and then you keep all your values in the my object so you don't have to use multiple refs or keep using myref.current all the time:

function MyComponent(props) {
const componentRef = useRef({});
const { current: my } = componentRef;


my.count = 42;
console.log(my.count); // 42


my.greet = "hello";
console.log(my.greet); // hello


return <div />;
}