Development/GraphQL

[GraphQL / Apollo] 오프라인 노트앱 만들기

안다희 2020. 2. 16. 03:13
728x90

https://www.youtube.com/watch?v=A4Grb-bm0Ok&list=PL7jH19IHhOLMTVBzUkbN74h1nyXW7VhYV

 

[Stack]

- GraphQL

- Apollo

- ReactJS

- StyledComponent

 

https://github.com/daheeahn/nomad-notes

 

daheeahn/nomad-notes

Offline-First Markdown Note Taking App bulit with Apollo오프라인 노트앱 만들기 - daheeahn/nomad-notes

github.com

 

GraphQL, Apollo로 local state 관리, 오프라인으로 !!

No 리덕스 No Context Api !!!!!

니꼴피셜: 리덕스 버림. Apollo, GQL과 함께면, 리덕스 필요 없음

 

GQL이 Redux의 API 부분만 대체하는줄 아는데,

GQL로 local state 다루는걸 보여줄거야. !!!! 와웅

 

다 local state야. no api~

use local storage

 

리덕스 -> GQL, Apollo 조합으로 바꾸길 바라!

나에게 너무나도 적합한 강의임

 

1. create react app

npx로 진행되는 앱을 만들어줄거야

npx는 create-react-app 없이 앱을 만들 수 있어.

그래서 create-react-app 다운 받을 필요 없음

단지 npx를 통해 앱을 실행 시키면 됨.

 

npx create-react-app nomad-notes

 

2. 깃헙 repo 만들기

https://github.com/daheeahn/nomad-notes

1에서 만든 폴더랑 연결시켜주기

 

3. 필요없는거 다 지우기

App.css

App.test.js

index.css

logo.svg

serviceWorker.js

setupTests.js

 

이 파일 관련 코드도 다 지우기!

 

4. 

Apllo boost: 앱에 대한 것들을 도와주는 gql 클라이언트인데, 이게 지금은 필요 없음.

Apollo boost에서 온 client는 http쪽으로 손볼게 넘 많아.

Api를 비롯한 다른 configuration도 그렇고, 

여기서 우리가 해야하는건 client를 만들건데, cache에서만 일하고 오프라인으로만 있을 클라이언트를 만들거야.

그래서 apllo-boost를 사용하지 않아.

 

yarn add apollo-cache-inmemory apollo-client graphql react-apollo styled-components styled-reset react-textarea-autosize graphql-tag apollo-link-state react-dom

graphql-tag: 그래야 query를 넣을 수 있어.

package.json에서 잘 추가됐는지 확인해보기!

 

5. 

styled-component 파일 만드는건 css 셋업은 생략할게

Styled-components는 typed component라고 한다.

 

6. client 생성. apollo를 오프라인으로 생성.

client: 오프라인, 캐싱 등등을 담당하게 하는거야

 

src/Components 폴더 생성

src/Routes 폴더 생성

 

src/Apollo.js 생성해서 Apollo 오프라인 셋업 할거고

필요한거 전부 import.

그리고 이 client를 react Apollo provider에 export 할거야

import { defaults, resolvers, typeDefs } from "./clientState";

import { ApolloClient } from "apollo-client";
import { ApolloLink } from "apollo-link";
import { InMemoryCache } from "apollo-cache-inmemory";
import { withClientState } from "apollo-link-state";

// apollo-boost 쓰면 이걸 자동으로 해줌.
// 자동으로 될 부분까지 전부 수동으로 해볼거야
// internet api 온라인 부분은 빼고말이야
const cache = new InMemoryCache();

const stateLink = withClientState({
  // default state가 필요해. notes는 타입 정의나 resolvers같은걸 필요로 해. 이부분에 앱에 필요한 모든 로직을 넣어야해
  // 그러니까 모든 로직을 웹에있는 clientState에 적어줘야 하는거야.
  // reseolver 같은 경우는 오프라인으로 해주고, 타입선언, default state도. 여기서 해줘야 해.
  // 왜냐면 여기는 client state니까
  // clientState는 cache가 필요해. 내가 clientState를 어디에 저장할건지 지정을 해줘야 해
  // 타입 정의도 필요해
  // 디폴트, 리졸버가 필요햐ㅐ. 이걸 전부 import
  cache,
  typeDefs,
  defaults,
  resolvers
}); // 아폴로에서 거의 모든 명령어들은 전부 링크가 돼. http link도 있고 error link, state link도 있어

// 여기선 http link & error link 만들거야. subscription을 위한 web socket을 넣거나.... 아니면 전부를 넣거나!
const client = new ApolloClient({
  cache,
  link: ApolloLink.from([stateLink])
});

export default client;

 

src/clientState.js

export const defaults = {};
export const resolvers = {};
export const typeDefs = {};

 

src/index.js

import { ApolloProvider } from "react-apollo"; // add this code
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
import client from "./Apollo"; // add this code

ReactDOM.render(
  <ApolloProvider client={client}> // add this code
    <App />
  </ApolloProvider>, // add this code
  document.getElementById("root")
);

 

 

7. yarn start

콘솔에서 에러 안나는지도 확인해주기!

이걸 apollo-boost로 하려고 했는데 http 링크가 필요해서 제대로 작동하지 않았어. 그래서 그냥 수동으로 했어

 

8. typeDef, resolvers, default

logic 다 훑어보고, apollo client dev tools로 보는법

모든 queries 여기서 실행되는 것도 보고 테스트할거야.

그리고 html css 넣는것까지 볼거임

 

9. typedef

schema를 documentation explorer 에서 사용할거야 (개발자도구-Apollo? 이게 gql 익스텐션일거임)

저 gql 익스텐션은 새로고침하면 바보가 됨. so, reload frame을 눌러줘야해 우클릭해서

 

src/clientState.js

export const defaults = {
  // 디폴트로 할 수 있는 쿼리
  notes: [
    {
      __typename: "Note", // apollo로 localState랑 일할 때는 이걸 꼭 써줘야해. 규칙.
      id: 1,
      title: "First",
      content: "Second"
    }
  ]
};
export const typeDefs = [
  // 우리의 schema가 어떤 형태인지 보여주는거야 by graphql 언어. 서버가 없기 때문에 이렇게 한댜
  `
    schema {
        query: Query
        mutation: Mutation 
    }
    type Query {
        notes: [Note]!
        note(id: Int!): Note
    }
    type Mutation {
        createNote(title: String!, content: String!): Note
        editNote(id: Int!, title: String, content: String): Note
    }
    type Note {
        id: Int!
        title: String!
        content: String!
    }
    `
]; // createNote랑 editNote는 return 없어. 따로 필요 없으니까!
export const resolvers = {
  Mutation: {},
  Query: {}
};

그리고 yarn start, Apollo 탭으로 가서 reload frame, load from cache 체크, 재생 버튼 눌러보면... @client 없이도 됨!

그리고 쿼리 입력!

like this~

 

?? @client 없으면 원래 어떤식으로 가는냐??????????????????????????????????????????????????????????????

왜 cache에서 처리하지 ㅇ낳지?????????????????

 

이 쿼리는 디폴트로 원래(?) 할 수 있다는데? 근데 내가 src/clientState.js 에

defaults로 notes라적어놔서 할 수 있었던게 아니라?? 모르겠다.

 

10. 버그 수정 (1.3.1) 다시봐야할듯 / queryStore 에러

Apollo 익스텐션에서 캐시를 제대로 보고 싶었는데, 이렇게 하면 버그 사라짐,,, 흠

 

App.js

import { GET_NOTES } from "./queries"; // add this line
import { Query } from "react-apollo"; // add this line
import React from "react";

function App() {
  return (
    <div className="App">
      <Query query={GET_NOTES}>{() => null}</Query> // add this line
      <p>hi</p>
    </div>
  );
}

export default App;

 

src/queries.js

?

 

근데 그럼에도 불구하고 나는 아예 익스텐션 안먹힘

 

 

 

11. Note Query

바로 

쿼리 하나를 입력할 수 있음. notes

{
  notes @client {
    id
    title
  }
}

 

react apollo는 dev tool이 하나 있는데, 디폴트 설정으로 오프라인 작업을 할 수없어.

시스템 상에서 API(: ???????)로 넘어가려고 할거야 그래서 load from cache를 할 수 없어

?? 그럼 react apollo tool의 문제?

그래서 @client를 하거나 // 이걸 하세요

재생 버튼 눌러도 될거야 (프레임 새로고침. 안되면 캐시 탭에서 프레임 새로고침.)

 

 

Reload Frame 계속 해주고~

 

12. 근데 Apollo 익스텐션에서 다시 우클릭 - 검사(Inspect) 누르면 또 뭐가 나오는데, 거기에 콘솔에 버그가 있음. 일단 쿼리는 되니까 쿼리부터 하자. 그리고 캐시도 되는데? 10번을 안해도 되는데

13. Query, Fragment

src/clientState.js

export const resolvers = {
  Mutation: {},
  Query: {
    // 보통의 gql resolver와 같다.
    note: (_, variables, context) => {
      // context엔 아무거나 넣어도 돼.
      console.log(variables);
      return null; // 언제나 resolver에서 무언가를 리턴 해야해!!!!!!!!!! 규칙이야 ******
    }
  }
};

그리고 익스텐션에서

  note(id: 1)@client {
    id
    title
  }
}

하면~~~ 콘솔에 variables가 찍힘!

@client가 있으면 어떤식이든 콘솔에 찍히긴 한다는거.

 

cache에서 id: 1 노트를 가져오는거야.

default에 note array 있으니까!

 

-----또 하나의 에러-----

Apollo Client Developer Tools에서 스키마를 인식하지 못하고 우측의 Documentation Explorer에 No Schema Available이라는 메시지가 뜬다면 확인해보세요. 제 경우에는 시스템 내 schema와 type Query가 이미 존재했기 때문에 typeDefs 안에 재선언 한 것이 문제가 되어 에러를 뿜었습니다. 첨부한 이미지처럼 schema와 type Query 앞에 extend를 붙여 확장함으로써 중복 선언을 피해주니 정상출력되는 것을 확인할 수 있었습니다.

나는 그래도 안됐음......

 

src/clientState.js

export const resolvers = {
  Mutation: {},
  Query: {
    // 보통의 gql resolver와 같다.
    note: (_, variables, { cache }) => {
      // context엔 아무거나 넣어도 돼.
      const id = cache.config.dataIdFromObject({
        __typename: "Note",
        id: variables.id
      }); // cache에서 정보 가져오기
      console.log(id);

      return null; // 언제나 resolver에서 무언가를 리턴 해야해!!!!!!!!!! 규칙이야 ******
    }
  }
};

이렇게 하고 note 쿼리 한 번 실행하면, Note:1 이 찍힌다 콘솔에!

 

이제 이 id를 가지고 있는 fragment(object같은거)를 찾을거야 gql apollo cache에서 찾을 수 있느 ㄴobj같은거

 

react apollo cache가 작동하는 방식이 notes array로 가서 찾는게 아니라 cache에서 fragment만 가져올수있는 방법이 따로 있어.

우리가 id가 있는 한! array가 따로 있는한!!! 상관없이 찾을 수 있다구.

 

src/fragment.js

import gql from "graphql-tag";

// 내가 재사용하고 싶은거야! 반복해서 입력하고 싶지 않을때. 수정.추가할 때나 하나만 찾고싶을때나... 암튼 그럴 때 fragment를 이용해 재사용 하는거야
export const NOTE_FRAGMENT = gql`
  fragment NoteParts on Note {
    id
    title
    content
  }
`;
// id title content는 clientState에서 type Note 에서 가져오는거야!

 

src/clientState.js

export const resolvers = {
  Mutation: {},
  Query: {
    // 보통의 gql resolver와 같다.
    note: (_, variables, { cache }) => {
      // context엔 아무거나 넣어도 돼.
      const id = cache.config.dataIdFromObject({ // 이게 devTools에서 작동하고, 다른거 getCacheKey? 이런건 안된대.. 왜지
        __typename: "Note",
        id: variables.id
      }); // cache에서 정보 가져오기
      console.log(id);

      // id만 있으면 cache에 있는 fragment를 가져올 수 있어
      const note = cache.readFragment({ fragment: NOTE_FRAGMENT, id });
      return note;
      //   return null; // 언제나 resolver에서 무언가를 리턴 해야해!!!!!!!!!! 규칙이야 ******
    }
  }
};

const note부분 추가

 

 

reload frame하고 id가 1인 노트를 찾아보자!

 

 

 

14. add note Mutation

src/clientState.js - resolvers에

// Query랑 동등한 위치에 추가 
  Mutation: {
    createNote: (_, variables, { cache }) => {
      // 먼저 cache에 있는 note arr를 가져온다.
      const noteQuery = cache.readQuery({ query: GET_NOTES });
      console.log(noteQuery);
    }
  }

 

src/queries.js 생성

// clinetState에 이 쿼리들 써야 함
import gql from "graphql-tag";

export const GET_NOTES = gql`
  {
    notes @client {
      id
      title
      content
    }
  }
`;

 

그리고 apollo 개발자도구 가서

mutation {
  createNote(title:"Hello", content: "what's up") @client {
    id
  }
}

돌려보면 콘솔이 찍힌다! @client 빼먹으면 콘솔 안찍힘

 

이제 제대로 해보자

  Mutation: {
    createNote: (_, variables, { cache }) => {
      // 먼저 cache에 있는 note arr를 가져온다.
      // const noteQuery = cache.readQuery({ query: GET_NOTES });
      const { notes } = cache.readQuery({ query: GET_NOTES }); // noteQuery안에 notes가 있으니까
      const { title, content } = variables;
      // 새로운 노트는 타입이 있어야 해. typename도.
      const newNote = {
        __typename: "Note", // apollo가 구성에 문제 없는지 자동으로 점검해줘 type에 없는 Diary 이런거 추가하면 안된다고 알려줘. 안전하지!
        title,
        content,
        id: notes.length + 1
      };
      cache.writeData({
        data: {
          notes: [newNote, ...notes] // ...notes 꼭 해줘야 해! 안그럼 날아가.
        }
      });
      console.log(notes);
      return newNote;
    }
  }

 

reload frame

load from cache 체크

mutation {
  createNote(title:"Hello", content: "what's up") @client {
    id
  }
}

그리고 

이렇게 쿼리 날리면 추가된 notes를 볼 수 있다!

 

근데 load from cache 체크하고 @client 없애고 note query 날려도 됨 (안될 때도 있는데 왜그런지는 몰라)

 

암튼! 그렇다!

 

이거를 command + r 하면 (새로고침) 다 없어지고 초기값인 id 1인 노트만 남겠지! note query 날리면.

근데 @client 없이 load from cache 체크해야 함!

 

 

15. edit note

// in Mutation
    editNote: (_, { id, title, content }, { cache }) => {
      const noteId = cache.config.dataIdFromObject({
        // 왜 하는거지? -> noteId: "Note:1" 이걸 ReadFragment할 때 넣어야 된다.
        __typename: "Note",
        id
      }); // cache에서 정보 가져오기
      const note = cache.readFragment({ fragment: NOTE_FRAGMENT, id: noteId });
      // writeData 대신 fragment 만들어줄거야. 한개의 fragment를 update할거거든
      const updatedNote = {
        ...note,
        title,
        content
      };
      cache.writeFragment({
        id: noteId,
        fragment: NOTE_FRAGMENT, // fragment 어떤 모양의 데이터 업데이트 할건지
        data: updatedNote
      });
      console.log(updatedNote);
      return updatedNote;
    }

이렇게 해도됨.

    editNote: (_, { id, title, content }, { cache }) => {
      //   const noteId = cache.config.dataIdFromObject({
      //     __typename: "Note",
      //     id
      //   });

      //   const note = cache.readFragment({
      //     fragment: NOTE_FRAGMENT,
      //     id: noteId
      //   });

      //   const updatedNote = {
      //     ...note,
      //     title,
      //     content
      //   };
      //   cache.writeFragment({
      //     id: noteId,
      //     fragment: NOTE_FRAGMENT,
      //     data: updatedNote
      //   });
      //   return updatedNote;

      const { notes } = cache.readQuery({ query: GET_NOTES });
      console.log(notes);
      const target = notes.find(n => n.id === id);
      target.title = title;
      target.content = content;

      cache.writeData({
        data: {
          notes: notes
        }
      });
      return target;
    }

+ 성능이 안좋긴 한데.... .이게 더 좋네ㅋ? 가독성이 좋네?ㅋ

 

 

 

 

 

mutation {
  editNote(id: 1, title: "new title", content: "new content") @client {
    id
  }
}

근데 title이나 content 없으면 원래 것이 들어간다. 근데 콘솔에 경고뜸

그냥 둘다 넣어줘야 하는걸로!

 

 

16. Router and Routes

16-1. 폴더 정리

src/Routes 폴더에

Add, Edit, Note, Notes 폴더 만든다

 

src/Components/App에

App.js 옮겨준다.

index.js 파일도 하나 만든다.

import App from "./App";
export default App;

 

16-2. apollo랑 query 써줄거야

 

4개의 라우터 만들어줄거야

 

우리가 전부를 볼 수 있는 라우트

나의 노트 볼 때

수정할때

노트추가할때 (form 비어있음)

 

crud.

 

Notes부터 만들어주자

 

src/Components/App/App.js

import { BrowserRouter, Route, Switch } from "react-router-dom";

import Notes from "../../Routes/Notes";
import Note from "../../Routes/Note";
import Add from "../../Routes/Add";
import Edit from "../../Routes/Edit";
import React from "react";

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact={true} path={"/"} component={Notes} />
        <Route path={"/note/:id"} component={Note} />
        <Route path={"/add"} component={Add} />
        <Route path={"/edit/:id"} component={Edit} />
      </Switch>
    </BrowserRouter>
  );
}

export default App;

exact는 Notes에만 있네

 

 

1) src/Routes/Notes/Notes.js

import React from "react";

export default class Notes extends React.Component {
  render() {
    return "hi";
  }
}

 

2) src/Routes/Notes/index.js

import Notes from "./Notes";
export default Notes;

 

1) 2) 를 Note, Add, Edit 폴더에도 똑같이 만들어준다. 이름만 변경하고!

 

 

17.  Notes Route

Query를 만들려면

 

src/Routes/Notes/Notes.js 

import { GET_NOTES } from "../../queries"; // shared query 이거 사용!!
import { Link } from "react-router-dom";
import Plus from "../../Assets/plus.png";
import { Query } from "react-apollo";
import React from "react";

const Notes = () => {
  console.log(Plus);
  return (
    <>
      <div style={{ display: "flex" }}>
        <h1>Dahee Note</h1>
        <Link to={"/add"}>
          <img src={Plus} width={50} height={50} />
        </Link>
      </div>
      <Query query={GET_NOTES}>
        {({ data }) => {
          console.log(data);
          return (
            data?.notes?.map(note => (
              <Link to={`/edit/${note.id}`} key={note.id}>
                <h5>{note.title}</h5>
              </Link>
            )) || null
          );
        }}
      </Query>
    </>
  );
};

export default Notes;

{ data } 주목! data.data라 destruct한거임

 

18. 노트를 클릭했을 때 변경되는 부분! note view

@client는 오프라인일 때 꼭 넣어줘야해 쿼리에

 

GET_NOTE는 많이 필요해 한개의노트 불러올 때, 수정을 할 때도 . 그래서 queries.js에 ~~

src/queries.js

import { NOTE_FRAGMENT } from "./fragment";
// clinetState에 이 쿼리들 써야 함
import gql from "graphql-tag";

// export const GET_NOTES = gql` // remove this code
//   {
//     notes @client {
//       id
//       title
//       content
//     }
//   }
// `;

export const GET_NOTES = gql`
  {
    notes @client {
      ...NoteParts // add this code
    }
  }
  ${NOTE_FRAGMENT} // add this code
`;

// add this code
export const GET_NOTE = gql`
  query getNote($id: Int!) {
    note(id: $id) @client {
      ...NoteParts
    }
  }
  ${NOTE_FRAGMENT}
`;

이 GET_NOTE를 이제 이용하면 되겠다!

 

- 마크다운 렌더링

yarn add react-markdown-renderer

 

src/Routes/Note/Note.js

import { GET_NOTE } from "../../queries";
import { Link } from "react-router-dom";
import MarkdownRenderer from "react-markdown-renderer";
import { Query } from "react-apollo";
import React from "react";

const Note = props => {
  const {
    match: {
      params: { id }
    }
  } = props;
  return (
    <Query query={GET_NOTE} variables={{ id }}>
      {({ data }) => {
        console.log("data", data);
        return (
          <>
            <h1>{data?.note?.title}</h1>
            <Link to={`/edit/${id}`}>
              <h4>Edit</h4>
            </Link>
            <MarkdownRenderer markdown={data?.note?.content} />
          </>
        );
      }}
    </Query>
  );
};

export default Note;

 

 

 

19. Form

edit 할 수 있는 form !

add에도 사용됨.

 

src/Components/Editor/index.js

src/Components/Editor/Editor.js

폴더랑 파일 만들기!

 

src/Components/Editor/index.js

import Editor from "./Editor";
export default Editor;

 

src/Components/Editor/Editor.js

import React, { useState } from "react";

import MarkdownRenderer from "react-markdown-renderer";

const Editor = ({ id: i, title: t, content: c, onSave }) => {
  const [id, setId] = useState(i || null);
  const [title, setTitle] = useState(t || "");
  const [content, setContent] = useState(c || "");

  return (
    <div>
      <input
        placeholder={"Untitled..."}
        style={{ borderColor: "transparent", fontSize: 40 }}
        value={title}
        onChange={e => setTitle(e.target.value)}
      />

      <br />

      <textarea
        rows={5}
        placeholder={"Content..."}
        style={{ borderColor: "transparent", fontSize: 20 }}
        value={content}
        onChange={e => setContent(e.target.value)}
      />

      <br />
      <MarkdownRenderer markdown={content} />

      <br />

      <button onClick={() => onSave(title, content, id)}>Save</button>
    </div>
  );
};

export default Editor;

대충 이렇게 세팅~

 

1) add

 

2) edit

 

render 할 때 중요한건, mount 전에 state 만들어주는거야.

 

 

onSave function은 add라우트를 통해 받을거야, edit은 또 다르겠지!

 

로컬변수는 useRef를 사용한다!

import React, { useRef } from 'react';

const RefSample = () => { 
  const id = useRef(1); 
  const setId = (n) => { id.current = n; } 
  const printId = () => { console.log(id.current); }
  return ( <div> refsample </div> ); 
};

.current로 바꾸고 조회하고 하면 된다!

 

 

19-1. Add

src/Routes/Add/Add.js

import React, { useRef } from "react";

import Editor from "../../Components/Editor";
import { Mutation } from "react-apollo";
import gql from "graphql-tag";

const CREATE_NOTE = gql`
  mutation createNote($title: String!, $content: String!) @client {
    createNote(title: $title, content: $content) {
      id
    }
  }
`;

const Add = ({ history: { push } }) => {
  // destruct 잘한다!!
  const createNoteRef = useRef(null);

  const _onSave = (title, content) => {
    if (title !== "" && content !== "") {
      createNoteRef.current({ variables: { title, content } });
      push("/"); // redirect
    }
  };

  return (
    <>
      <Mutation mutation={CREATE_NOTE}>
        {createNote => {
          createNoteRef.current = createNote;
          return <Editor onSave={_onSave} />;
        }}
      </Mutation>
    </>
  );
};

export default Add;

- push: 리다이렉트 하는거임 (props 안에 history가 있음)

- <Mutation>

 

19-2. Edit

import { Mutation, Query } from "react-apollo";
import React, { useRef } from "react";

import Editor from "../../Components/Editor";
import { GET_NOTE } from "../../queries";
import gql from "graphql-tag";

export const EDIT_NOTE = gql`
  mutation editNote($id: Int!, $title: String!, $content: String!) @client {
    editNote(id: $id, title: $title, content: $content)
  }
`;

const Edit = props => {
  const {
    match: {
      params: { id }
    }
  } = props;

  const _onSave = (title, content, id) => {
    const {
      history: { push }
    } = props;
    if (title !== "" && content !== "" && id) {
      editNoteRef.current({ variables: { title, content, id } });
      //   push(`/note/${id}`)
      push("");
    }
  };

  const editNoteRef = useRef(null);

  return (
    <>
      <Query query={GET_NOTE} variables={{ id }}>
        {({ data }) =>
          data?.note ? (
            <Mutation mutation={EDIT_NOTE}>
              {editNote => {
                editNoteRef.current = editNote;
                return (
                  <Editor
                    onSave={_onSave}
                    title={data.note.title}
                    content={data.note.content}
                    id={data.note.id}
                  />
                );
              }}
            </Mutation>
          ) : null
        }
      </Query>
    </>
  );
};

export default Edit;

 

<Mutation>

{editNote

이게 내가 정의한 editNote함수가 오는건가?

 

 

 

20. 오프라인 Saving the Notes Offline

Note를 만들었을 때나 수정할 때 function이 작동하게 할거임. query를 읽는거야.

cache에서 모든 노트 불러오고, local storage에 저장하는거임.

 

src/offline.js 생성

import { GET_NOTES } from "./queries";

export const saveNotes = cache => {
  // query를 읽게 함
  // note를 저장하면 cache를 주고 노트가 저장되면 모든 노트의 arr를 받는거지. (이건 clientState에서 하는거같은데) local storage에도 저장하고!
  const { notes } = cache.readQuery({ query: GET_NOTES }); // noteQuery안에 notes가 있으니까
  const jsonNotes = JSON.stringify(notes);
  try {
    localStorage.setItem("notes", jsonNotes);
  } catch (error) {
    console.log(error);
  }
};

// title을 바꿨으면 나는 모든 앱의 리스트 불러오고, 업데이트 된 리스트를 로컬 스토리지에 옮겨주는거지.

 

src/clientState.js

Mutation - createNote, editNote return 직전에 saveNotes(cache) 넣어주기!

saveNotes(cache); // cache is from resolvers

 

 

개발자도구 - Application - Storage

이렇게 로컬스토리지 볼 수 있음!

key값 변경도 가능 이게 바뀌면 restore 못하겠지

 

21. Restore

 

src/clientState.js

import { saveNotes, restoreNotes } from "./offline";

export const defaults = {
  // 디폴트로 할 수 있는 쿼리
  notes: restoreNotes()
};

이제 디폴트로 로컬스토리지에 있는 노트를 가져온다!

 

src/offline.js

export const restoreNotes = () => {
  const notes = localStorage.getItem("notes");
  if (notes) {
    try {
      const parsedNotes = JSON.parse(notes);
      return parsedNotes;
    } catch (error) {
      console.log(error);
      return [];
    }
  }
  return [];
};

 

개발자도구 - Apollo - cache & reload frame

cache에도 있을 것!

 

local state를 위한 dev tool이 아직 없다면...

api 작업을 해야한다면 graphql이 최선일거야

현재 tool이 별로 없는지라.

무슨말이지 이게?

 

 

redux랑 비교했을 때 loggers, middlewares 같은 것들이 많으니까

시간이 얼마나 걸리냐의 차이겠지.

 

 

[코드 챌린지]

날짜 sort

creation date로 sort하기

지워도 보기

 

 

 

 

** 추가사항

TextareaAutoSize

const input = styled(TextareaAutoSize)`~`

유저가 스크롤 안해도 높이에 맞춰 텍스트박스 맞춰주는거.

 

 

 

** ????????

apollo-boost client

local state

더 단순한 방법이.... 분명히 있을거야@!!!!!!!!!!!!!!!!!!!!!!!!!!!

'Development > GraphQL' 카테고리의 다른 글

GQL서버 init  (0) 2020.04.10
[GraphQL] passport로 인증기능 만들기  (2) 2020.03.23
[GraphQL] apollo-server와 express 연결하기  (0) 2020.03.22
[GraphQL / Apollo] 도입해보기  (0) 2020.03.02
출처: https://mingos-habitat.tistory.com/34 [밍고의서식지:티스토리]