比较用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来渲染组件。


 

posted @ 2018-09-03 18:00  tutu_python  阅读(309)  评论(0)    收藏  举报