Controllers & Springs
This is an advanced guide into the workings of react-spring with some prerequisites of your own knowledge. You should be confident with classes & Typescript before reading this.
What you know
If you've already used the useSpring
hook then you'll be familiar with the following code:
const [styles, api] = useSpring(() => ({
x: 0,
y: 0,
backgroundColor: '#ff0000',
scale: [1, 1, 1],
config: {
precision: 0.0001,
},
}))
If we look only at the return value from our hook we can see it's an array with two items styles
and api
.
We'll initially focus on what styles
are. This is an object
of SpringValue
s where the key of said object
correlates to the animatable keys referenced in the config object you passed (either in the way above or as part
of the from
config object). The value of said key is a SpringValue
.
Explained in typescript
terms the signature looks like:
type SpringValues<SpringConfig extends Record<string, any> = {
[Key in keyof OnlyAnimatableKeys<SpringConfig>]: SpringValue
}
In the example above, OnlyAnimatableKeys
would narrow SpringConfig
to only include the keys x, y, backgroundColor, scale
.
Then because we know they're animatable, they will therefore be SpringValue
s note this is a simplified version of the types
in the library.
The second item in the array, api
is a SpringRef
which is an abstraction around the methods of the Controller
class.
However, a SpringRef
can manage multiple Controller
s. For more information, see Imperative Api
.
Controller
So where does the Controller
come into all this? Well, every "spring" is infact, a Controller
. Therefore, when you use the
hook useSpring
you initialise a new Controller
class and when you pass the number X to the useSprings
hook, you're creating
X amount of Controller
s.
These Controller
s manage the SpringValue
s you create in your config object. It's methods are very similar to that of the
SpringValue
class, the primary methods used such as start
, stop
, pause
run through the
array of managed SpringValues and call the exact same method:
// The set method from the Controller class
set(values) {
for (const key in values) {
const value = values[key]
if (!is.und(value)) {
this.springs[key].set(value)
}
}
}
// Would be used like this
controller.set({
x: 0,
scale: [0,0,0]
})
The signature of useSpring
hook's config object is identical to the Controller
class constructor first argument. You can
therefore draw the conclusion that the useSpring
hook handles the lifecycle of the Controller
class in the react environment
and adds the controllers to a provided (or generated in the hook) SpringRef
, providing a very straight forward and clear
interface for managing one or more Controller
classes. Meaning, if you so choose, you could omit using a hook and instead
use Controller
class directly!
For a more detailed API description, see the advanced api reference entry for Controller.
Spring value
SpringValues are what you normally interact with, they're the props that are specifically passed to the animated
component,
they can be interpolated and don't necessarily need to be named after properties of the element they're being used on:
const {
backgroundColor, // SpringValue<string>
o, // SpringValue<number>
trans, // SpringValue<number[]>
} = useSpring({
backgroundColor: '#00ff00',
o: 0,
trans: [0, 1, 2],
})
This is because the names you give are just the keys used in the Controller
and the SpringValue
only cares about the type of
value you're passing. Inside the SpringValue
class we have the entire lifecycle of the animation, from the event handlers
being called to the type of advancement being used (e.g. spring physics
or duration
) the SpringValue
is the driving force
for your animation.
In addition to controlling a "Fluid value" – a value that changes over time (see Fluids for more information). SpringValues
also apply their updates to the animated node
they're passed to. You might recall if you had read the animated elements
concept page that on creation of the animated
component, we convert any SpringValues
into AnimatedValues
which seems like a one way direction,
infact we attach this AnimatedValue
to the original SpringValue
, which allows the spring to update the animated node (e.g. <animated.p>
.)
via the animated value when the advance
function is called on the SpringValue
via our rafz
package. SpringValue
is able to do this
because it extends the FrameValue
which is a kind of FluidValue
.
// Taken from `@react-spring/core/src/SpringValue.ts
class SpringValue<T = any> extends FrameValue<T>
// Taken from `@react-spring/core/src/FrameValue.ts
abstract class FrameValue<T = any> extends FluidValue<
T,
FrameValue.Event<T>
>
// Taken from `@react-spring/shared/src/FluidValue.ts`
abstract class FluidValue<T = any, E extends FluidEvent<T> = any>
Similar to the Controller
class because it does not rely on react internals (this is how we animate outside the react render system) you can
use this class directly, however the lifecycle events that are associated with Controllers & hooks will not be applied and is something you
would need to manage yourself. For a more detailed API description of SpringValue
, see the advanced api reference
entry for SpringValue
.
Frame value
The first thing you'll notice about the FrameValue
class is that is considered "abstract", this is a feature unique typescript
and means that
you cannot instantiate the class directly. The point of an abstract class is to provide a base class that other classes can extend from in their shape
(methods / properties) but usually require some additional implementation. In the case of FrameValue
we have the advance
method which is abstract,
the SpringValue
class extends FrameValue
and implements it's own advance
method. You can read more about abstract classes here.
Fluids
Fluids is a small glue layer for observable events. It allows parent nodes send events to their children creating an event-driven system. Therefore,
when a FluidObserver
has an event observered, it can perform an action. In the case of SpringValue
, the _start
function is called, thus animating
the SpringValue
's value, which in turn animates the animated
node you're passing this value too.
Fluids are used all over this library, in the case of our animated
HOC, we use Fluids to schedule animated updates with our rafz
package.
If you want to learn more about Fluids, I recommend looking at our source code!
What you learnt
You've probably learnt alot about how the internals of react-spring
works! But more importantly, you've specifically learnt about these things:
- How the
useSpring
hook works under the hood - What is a
Controller
&SpringValue
- How we have an event-driven system embeded in the library!