比较用apollo-link-state管理本地状态和自己管理本地状态的区别
以完成Todos为例
一、在不使用apollo-link-state的情况下编写Todos:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
component/App.js
import React, { Component } from 'react';
import TodoForm from './TodoForm';
import TodoList from './TodoList';
import TodoFooter from './TodoFooter';
let id=0;
class App extends Component {
constructor(props){
super(props);
this.state={
todos:[],
filter:"all",
}
}
handleSubmit=(value)=>{
this.setState({todos:this.state.todos.concat({id:id++,value:value,completed:false})})
}
handleComplete=(id)=>{
const newtodos=this.state.todos.map((todo)=>{
if(todo.id===id){
return {...todo,completed:!todo.completed}
}else{
return todo
}
})
this.setState({
todos:newtodos
})
}
setFilter=(filter)=>{
this.setState({filter:filter})
}
getFilterTodos=(todos,filter)=>{
if(filter==="all"){
return todos
}else if(filter==="completed"){
return todos.filter((todo)=>todo.completed)
}else if(filter==="uncompleted"){
return todos.filter((todo)=>!todo.completed)
}else{
return []
}
}
render() {
return (
<div>
<TodoForm handleSubmit={this.handleSubmit}/>
<TodoList todos={this.getFilterTodos(this.state.todos,this.state.filter)} complete={this.handleComplete}/>
<TodoFooter setFilter={this.setFilter} filter={this.state.filter}/>
</div>
);
}
}
export default App;
我们需要在最顶层的App中来管理state,然后需要将todos传递给TodoList,把filter传递给TodoFooter,同时需要为todos和filter的修改分别指定一个回调函数,以便子组件事件发生时回调App中的相关函数。回调的层数取决于我们组建的复杂程度,组建层次越多,回调次数越多。同时向下传递的state越长。
component/TodoFooter.js
import React, { Component } from 'react';
export default class TodoList extends Component{
handleClick=(filter)=>{
this.props.setFilter(filter)
}
render(){
return(
<div>
<Link filter="all" handleClick={this.handleClick} active={this.props.filter}>All</Link>
{' , '}
<Link filter="completed" handleClick={this.handleClick} active={this.props.filter}>Completed</Link>
{' , '}
<Link filter="uncompleted" handleClick={this.handleClick} active={this.props.filter}>Uncompleted</Link>
</div>
);
}
}
class Link extends Component{
handleClick=(event)=>{
event.preventDefault();
this.props.handleClick(this.props.filter);
}
render(){
if(this.props.active===this.props.filter){
return <span>{this.props.children}</span>
}
return (
<a
href="#"
onClick={this.handleClick}
>{this.props.children}</a>);
}
}
component/TodoForm.js
import React, { Component } from 'react';
export default class TodoForm extends Component{
constructor(props){
super(props);
this.state={
value:"",
}
}
handleChange=(event)=>{
this.setState({value:event.target.value})
}
handleSubmit=(event)=>{
event.preventDefault();
this.props.handleSubmit(this.state.value)
this.setState({value:""})
}
render(){
return(
<form onSubmit={this.handleSubmit}>
<input
type='text'
placeholder="add todo"
value={this.state.value}
onChange={this.handleChange}
/>
<input type='submit' />
</form>
);
}
}
component/TodoList.js
import React, { Component } from 'react';
export default class TodoList extends Component{
complete=(id)=>{
this.props.complete(id)
}
render(){
return(
<ul>
{this.props.todos.map((todo,i)=><li
key={todo.id}
onClick={()=>this.complete(todo.id)}
style={{
textDecoration: todo.completed ? 'line-through' : 'none',
}}
>
{todo.value}
</li>)}
</ul>
);
}
}
没有使用redux和apollo-state-client的情况下我们就需要层层的通过props向下传递state,并且需要层层的向上回调函数修改state.
下面使用apollo-state-client来编写待办事项app
首先我们要确定客户端的schema,即typeDefs:

看下我们的app.
显示信息:
为了显示我们需要两个信息即:todos用于展示代办事项,filter用于确定SHOW的激活状态。
因此在type query中应该有{todos和filter}
更改信息:
form提交表单时我们增加一个todo到todos,因此需要有一个addTodo
当我们点击一个todo的时候,我们希望改变todo的完成状态,需要一个toggleTodo。
todos的类别
定义Todo 需要有id,text,completed。id便于查找todo,text是要显示的值,completed代表待办事项的完成状态。
因此我们定义typeDefs.js如下:
export const typeDefs= `
type Todo{
id:Int!
text:String!
completed:Boolean!
}
type Mutation{
addTodo(text:String!):Todo
toggleTodo(id:Int!):Todo
}
type Query{
visibilityFilter:string
todos:[Todo]
}
`;
相应的初始化数据和解析函数:
resolvers.js
import gql from 'graphql-tag';
export const defaults={
todos:[],
visibilityFilter:"SHOW_ALL",
}
let nextTodoId=0;
export const resolvers={
Mutation:{
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',//为什么要增加__typename:TodoItem?
};
const data = {
todos:previous.todos.concat([newTodo])
};
cache.writeData({data});
return newTodo;
},
toggleTodo:(_,variables,{cache})=>{
const id = `TodoItem:${variables.id}`;//为什么id前要加TodoItem?
const fragment=gql`
fragment completeTodo on TodoItem{
completed
}
`
const todo = cache.readFragment({fragment,id});//readFragment和readQurey的区别
const data = {...todo,completed:!todo.completed};
cache.writeData({id,data})
return null;
},
},
};
定义了resolvers,typeDefs,defaults我们便可以使用clientState来初始化ApolloClient.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import ApolloClient from 'apollo-boost';
import {ApolloProvider} from 'react-apollo'
import {typeDefs} from './typeDefs';
import {resolvers,defaults} from './resolvers';
import './index.css';
import App from './component/App';
import registerServiceWorker from './registerServiceWorker';
const client = new ApolloClient({
clientState:{
defaults,
resolvers,
typeDefs,
}
})
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root'));
registerServiceWorker();
这样,我们便可以在App中使用Mutation和Query组件来进行修改和查询了。
定义App,App包含三块,最上面的TodoForm,中间的TodoList,最下面的TodoFooter.
component/App.js
import React from 'react';
import Footer from './Footer';
import TodoForm from './TodoForm';
import TodoList from './TodoList';
const App = () => (
<div>
<TodoForm />
<TodoList />
<Footer />
</div>
);
export default App;
定义TodoForm,TodoForm需要使用Mutation组件来增加todo.
TodoForm.js
import React from 'react';
import {Mutation} from 'react-apollo';
import gql from 'graphql-tag';
const ADD_TODO = gql`
mutation addTodo($text:String!){
addTodo(text:$text)@client{
id
}
}
`
const TodoForm = ()=>(
<Mutation mutation={ADD_TODO}>
{
addTodo=>{
let input;
return (
<div>
<form
onSubmit={(event)=>{
event.preventDefault();
if(!input.value.trim()){
return;
}
addTodo({variables:{text:input.value}})
input.value=""
}}
>
<input
type='text'
ref={node=>{input=node;}}
/>
<button type='submit'>add Todo</button>
</form>
</div>
);
}
}
</Mutation>
)
export default TodoForm;
我们使用addTodo直接修改cache中的todos.不需要再使用state和setstate来自己维护一个todos了。
定义TodoList,todoList需要首先查询todos和filter,根据todos和filter确定需要展示的数据,并展示出来。对于todo,我们需要使用Mutation来修改todo的completed的状态。
import React from 'react';
import {Query,Mutation} from 'react-apollo';
import gql from 'graphql-tag';
const TOGGLE_TODO=gql`
mutation ToggleTodo($id:Int!){
toggleTodo(id:$id)@client
}
`;
const Todo=({id,text,completed})=>(
<Mutation mutation={TOGGLE_TODO} variables={{ id }}>
{
(toggleTodo)=>(
<li
onClick={()=>toggleTodo(id)}
style={{
textDecoration: completed ? 'line-through' : 'none',
}}
>
{text}
</li>
)
}
</Mutation>
)
const GET_TODOS=gql`
{
todos @client{
id
text
completed
}
visibilityFilter@client
}
`;
const getVisibilityTodos=(todos,filter)=>{
switch (filter) {
case "SHOW_ALL":
return todos;
case "SHOW_ACTIVE":
return todos.filter((todo)=>!todo.completed)
case "SHOW_COMPLETED":
return todos.filter((todo)=>todo.completed)
default:
return new Error('Unknown filter: ' + filter);
}
}
const TodoList=()=>(
<Query query={GET_TODOS}>
{
({data:{todos,visibilityFilter}})=>(
<ul>
{
getVisibilityTodos(todos,visibilityFilter).map(todo=>(<Todo key={todo.id} {...todo}></Todo>))
}
</ul>
)
}
</Query>
)
export default TodoList;
定义TodoFooter,需要查询filter,以便决定激活的SHOW是哪一个。这里我们还需要一个修改,就是当点击代办事项的时候我们修改代办事项的完成状态,这里我们使用了直接修改的方法,即使用client.writeData().
import React from 'react';
import {Query} from 'react-apollo';
import gql from 'graphql-tag';
const GET_VISIBILITY_FILTER=gql`
{
visibilityFilter@client
}
`
const Link=({active,children,onClick})=>{
if(active){
return <span>{children}</span>
}
return(
<a
href="#"
onClick={(event)=>{
event.preventDefault();
onClick();
}}
>
{children}
</a>
);
}
const FilterLink=({filter,children})=>(
<Query query={GET_VISIBILITY_FILTER}>
{
({data,client})=>(
<Link
onClick={()=>client.writeData({data:{visibilityFilter:filter}})}
active={data.visibilityFilter===filter}
>{children}</Link>
)
}
</Query>
)
const TodoFooter=()=>(
<p>
SHOW:
<FilterLink filter="SHOW_ALL">ALL</FilterLink>
{','}
<FilterLink filter="SHOW_ACTIVE">ACTIVE</FilterLink>
{','}
<FilterLink filter="SHOW_COMPLETED">COMPLETED</FilterLink>
</p>
)
export default TodoFooter;
这里我们采用了三层设计,最底层的Link只用来展示《标签》,中间的FilterLink用来逻辑的判断,最上层TodoFooter用来展示数据。
这样我们就使用apollo-link-state完成了TODO app的设计。
通过比较代码可以看出,在本案例中自己管理state的方法要更加的简便。因为我们涉及的state仅有两个todos和filter,组件的层次也不深,回调函数回调层次也比较少,因此自己管理state更加的简便。
使用apollo-link-state我们做到了state管理与component展示相分离,使用typeDefs和resolvers来处理state,然后在组件展示和修改中只需要使用Mutation和Query组件结合Graphql语句来调用已经定义好的typeDefs和resolvers来渲染组件。
浙公网安备 33010602011771号