Derived State
In some scenarios, components need to perform further calculations on the State in Model before they can be used in the components. This logic can be directly written in the component or implemented through derived states in Model. Derived states are defined under the computed
field in the Model. Depending on the dependencies of the Model and the return type, there are three ways to define derived states.
Only Depend on the Model's Own State
The derived state only depends on the current Model's State, which is passed as the first parameter to the derived state's definition function.
For example, the todo application has items
and filter
in its State, and filter
is used to filter the todo items displayed on the current page. Therefore, we define a visibleTodos
derived state that can be directly used in the component. The sample code is as follows:
/**
* Assuming the structure of the todo item is as follows:
{
id: string; // id
text: string; // todo
completed: boolean;
}
**/
const todoModel = model('todo').define({
state: {
items: [],
filter: 'ALL', // ALL: show all;COMPLETED: show completed;ACTIVE: show active
},
computed: {
visibleTodos: state => {
switch (state.filter) {
case 'ALL':
return state.items;
case 'COMPLETED':
return todos.filter(t => t.completed);
case 'ACTIVE':
return todos.filter(t => !t.completed);
default:
return [];
}
},
},
});
Derived state will eventually be merged with the Model's State, so the derived state can be accessed through the Model's State object. For example, the visibleTodos
can be used in the component as follows:
function Todo() {
const [state, actions] = useModel(todoModel);
return (
<div>
<div>
{state.visibleTodos.map(item => (
<div key={item.id}>{item.text}</div>
))}
</div>
</div>
);
}
Dependent State from Other Models
In addition to depending on the current model's state, derived states may also depend on the state of other models. In this case, the definition format for the derived state is:
[stateKey]: [...depModels, (selfState, ...depModels) => computedState]
The following example demonstrates how the derived state combinedValue
of barModel
depends on the state of fooModel
.
const fooModel = model('foo').define({
state: {
value: 'foo',
},
});
const barModel = model('bar').define({
state: {
value: 'foo',
},
computed: {
combinedValue: [
fooModel,
(state, fooState) => state.value + fooState.value,
],
},
});
Derived State with Function Type
Derived states can also return a function. In this case, the definition format for the derived state is:
[stateKey]: (state) => (...args) => computedState // Only relies on its own state
[stateKey]: [...depModels, (selfState, ...depModels) => (...args) => computedState] // Relies on the state of other models
Assuming the filter
state is not stored in the state of the todo app, but is instead used directly in the component, visibleTodos
can be a function type value. This function receives the filter
parameter when used in the component, as shown below:
const todoModel = model('todo').define({
state: {
items: [],
},
computed: {
visibleTodos: state => filter => {
switch (filter) {
case 'ALL':
return state.items;
case 'COMPLETED':
return todos.filter(t => t.completed);
case 'ACTIVE':
return todos.filter(t => !t.completed);
default:
return [];
}
},
},
});
function Todo(props) {
// use props pass filter
const { filter } = props;
const [state, actions] = useModel(todoModel);
// get final todos
const todos = state.visibleTodos(filter);
return (
<div>
<div>
{todos.map(item => (
<div key={item.id}>{item.text}</div>
))}
</div>
</div>
);
}