- The Future of State Management
- Peggy Rayzis
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: yct21
As an application grows in size, the application states it contains generally become more complex. As developers, we may have to coordinate data sent from multiple remote servers as well as manage local data involving UI interactions. We need to store this data in a way that makes it easy for components in the application to access it.
Many developers have told us that the Apollo Client is a good way to manage remote data, which typically makes up about 80% of the total. What about the remaining 20% of local data (such as global flags, results returned by device apis, and so on)?
In the past, Apollo users used a separate Redux/Mobx store to manage this part of their local data. This was a viable solution during Apollo Client 1.0. But as The Apollo Client moved to version 2.0 and no longer relied on Redux, synchronizing data locally and remotely became trickier than ever. We received a lot of feedback from users, and we hope to come up with a solution that can encapsulate the entire application state in Apollo Client, so as to achieve a single source of truth.
The foundation for problem solving
We know this problem needs to be solved, so let’s think about, how do we properly manage state in the Apollo Client? First, let’s review what we like about Redux, such as its development tools and the Connect function that binds components to application state. We also need to consider the pain points of Using Redux, such as cumbersome boilerplate code, and the core requirements of using Redux, such as asynchronous Action Creator, or the implementation of state caching, or the adoption of positive interface policies, often need to be implemented by ourselves.
To implement an ideal state management scheme, we should take the long view of Redux and abandon the short view. In addition, GraphQL has the ability to integrate requests to multiple data sources into a single query, which we’ll take full advantage of here.
This is the data flow architecture of the Apollo Client.
GraphQL: Once learned, usable everywhere
A common misconception about GraphQL is that implementation of GraphQL depends on a specific server-side implementation. In fact, GraphQL is very flexible. GraphQL doesn’t care if the request is sent to a gRPC server, a REST endpoint, or a client cache. GraphQL is a generic language for data that has nothing to do with where the data comes from.
This is why Query and mutation in GraphQL are perfect descriptions of application state. Instead of sending an action, you can use GraphQL mutation to express application state changes. When querying application state, GraphQL Query can also describe the data required by the component in a declarative manner.
One of the biggest advantages of GraphQL is that a single Query can fetch data from multiple data sources, both local and remote, when appropriate GraphQL instructions are applied to the fields in a GraphQL statement. Let’s look at how.
State management in the Apollo Client
Apollo Link is Apollo’s modular networking stack that can be used to insert hook code at any stage in the life cycle of a GraphQL request. Apollo Link makes it possible to manage data locally within the Apollo Client. To get data from a GraphQL server, use HttpLink. To request data from Apollo’s cache, use a new Link: Apollo – link – state.
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink } from 'apollo-link';
import { withClientState } from 'apollo-link-state';
import { HttpLink } from 'apollo-link-http';
import { defaults, resolvers } from './resolvers/todos';
const cache = new InMemoryCache();
const stateLink = withClientState({ resolvers, cache, defaults });
const client = new ApolloClient({
cache,
link: ApolloLink.from([stateLink, new HttpLink()]),
});
Copy the code
This code initializes the Apollo Client using apollo-link-state.
To initialize a state link, call withClientState on Apollo Link with an object containing resolvers, defaults, and cache fields. The state link is then added to the Apollo Client link chain. This state link should precede HttpLink so that local Query and mutation are intercepted before being sent to the server.
Defaults
The defaults field is an object that represents initial state values. When the state link is first created, this default value is written to the Apollo Client cache. Although not required, preheating the cache is an important step, and passing in default prevents components from failing to query for data.
export const defaults = {
visibilityFilter: 'SHOW_ALL',
todos: [],
};
Copy the code
The defaults of the above code represent the initial values of Apollo cache.
Resolvers
After using the Apollo Client to manage application state, the Apollo Cache becomes a single data source for the application, containing both local and remote data. So how do we query and update the data in the cache? This is where the Resolver comes in. If you’ve used GraphQL-Tools on the server before, you’ll notice that the type signature for both resolvers is the same.
fieldName: (obj, args, context, info) => result;
Copy the code
If you haven’t seen this type signature before, just remember two important points: the query or mutation variables are passed to the resolver as args; The Apollo cache is passed to the Resolver as part of the context parameter.
export const defaults = { // same as before }
export const resolvers = {
Mutation: {
visibilityFilter: (_, { filter }, { cache }) => {
cache.writeData({ data: { visibilityFilter: filter } });
return null;
},
addTodo: (_, { text }, { cache }) => {
const query = gql`
query GetTodos {
todos @client {
id
text
completed
}
}
`;
const previous = cache.readQuery({ query });
const newTodo = {
id: nextTodoId++,
text,
completed: false,
__typename: 'TodoItem'}; const data = { todos: previous.todos.concat([newTodo]), }; cache.writeData({ data });returnnewTodo; }}},Copy the code
The Resolver function above is a way to query and update Apollo cache.
To writeData to the root of the Apollo cache, call the cache.writedata method and pass in the corresponding data. Sometimes we need to write data that depends on data that is already in the Apollo cache, such as the addTodo method above. In this case, the data can be queried with cache.readQuery before writing. To write a fragment for an existing object, pass in the optional id parameter, which is the cache index of the corresponding object. We used InMemoryCache above, so the form of the index should be __typename: ID.
Apollo-link-state supports the asynchronous resolver method, which can be used to perform asynchronous side effects, such as accessing some device apis. However, we do not recommend making requests to REST endpoints in resolver. Accurate method is to use [Apollo – link – rest] (https://github.com/apollographql/apollo-link-rest), the bag containing the @ rest orders.
@client
instruction
After the application’s UI triggers a mutation, Apollo’s network stack needs to know whether the data to be updated exists on the client or server side. Apollo-link-state uses the @client directive to mark fields that only need to exist locally on the client. Apollo-link-state then invokes the corresponding resolver method on these fields.
const SET_VISIBILITY = gql`
mutation SetFilter($filter: String!) {
visibilityFilter(filter: $filter) @client
}
`;
const setVisibilityFilter = graphql(SET_VISIBILITY, {
props: ({ mutate, ownProps }) => ({
onClick: () => mutate({ variables: { filter: ownProps.filter } }),
}),
});
Copy the code
This code restricts data modification locally via the @Client directive.
The form of Query is similar to mutation. If an asynchronous query is used in query, Apollo Client tracks the status of data loading and errors for you. If React is used, you can find the corresponding data in this.props. Data of the component, which also has many auxiliary methods, such as rescheduling, paging, and polling.
One of the exciting features of GraphQL is the ability to request data from multiple data sources in a single Query. In the following example, we query user data stored in the GraphQL server and visibilityFilter data stored in the Apollo cache in the same query.
const GET_USERS_ACTIVE_TODOS = gql`
{
visibilityFilter @client
user(id: 1) {
name
address
}
}
`;
const withActiveState = graphql(GET_USERS_ACTIVE_TODOS, {
props: ({ ownProps, data }) => ({
active: ownProps.filter === data.visibilityFilter,
data,
}),
});
Copy the code
This code uses the @client directive to query the Apollo cache.
More examples and tips for integrating Apollo-link-State into your application can be found on our latest documentation page.
Roadmap prior to 1.0
While apollo-Link-State development is stable enough for practical use, there are a few features we hope to implement soon:
- Client data mode: Currently, we do not support type validation of client data schema structures, because if the data schema is to be used for runtime construction and validation, the
graphql-js
Modules placed in dependencies can significantly increase the size of the site resource file. To avoid this, we would like to move the construction of the data schema to the construction phase of the project to support type validation, as well as all the cool features in GraphiQL. - Ancillary components: Our goal is to make Apollo’s state management as seamless as possible with the application. We will write React components to make it easier to implement common requirements, such as allowing variables in the program to be passed directly as parameters to a mutation at the code level, and then implementing mutation directly internally.
If you’re interested, you can join us on GitHub for our development and discussion, or go to Apollo Slack’s #local-state channel. Join us in building the next generation of state management!
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.