about 1 month ago

GraphQL一言以蔽之就是 query or mutation object 上面的特定的 field

Schema & Query

Schema, 指的是 server side 定義好的資料結構
Query, 指的是 client side 符合 schema 的查詢語言

Ex1
# Schema
type User {
  name
}

type Query {
  me: User
}

# Query
query {
  me {
    name
    age
  }
}

Field & Type

Type 定義了 query(或 mutation)的資料應該長什麼樣子

object 上的 field 都會都對應的 type, 可以分成兩大類

  1. Scalar Type: 基礎型別, 如 Int, String, Boolean
  2. Object Type: 物件型別, 屬於自定義資料結構, 並可以展開

用 OO 來類比 GraphQL

Class Person {
  constructor(name) {
      this.name = name;
    }
}

const user = new Person('Tom')
user.name // => Tom

Person 是一個 Class, user 是他的 instance, 該 instance 有一個 field name, 值為 Tom

延續 Ex1

User 是一個 Object Type, me (filed) 使用 User 作為 Type, me 擁有一個 scalar type name

Resolver

Resolver 定義當取得資料的實作

// data

const usersData = [
  { name: 'Tim' },
  { name: 'Tom' }
]

// schema

gql`
  type User {
    name: String
  }
  
  # Query 作為 query 的進入點
  type Query {
    users: User
  }
`

// resolver

{
  Query: {
    users: () => usersData
  }
}

// query

{
  users {
    name
  }
}

// result

{
  data: {
    [
      { name: 'Tim' },
      { name: 'Tom' }
    ]
  }
}

Operation Name, Aliases, Fragment, Arguments, Variables

Argments.js
// data

const usersData = [
  {
    name: 'Tim',
    id: 1
  },
]

// schema

gql`
  type User {
    name: String
    id: Int
  }
  
  # Query 作為 query 的進入點
  type Query {
    user(name: String!): User
  }
`

// resolver

{
  Query: {
    user: (root, args, context) => {
      const { name } = args;
      return usersData.find((user) => user.name === name)
    }
  }
}

// query

{
  user(name: 'Tim') {
    id
  }
}

// result

{
  data: {
    id: 1
  }
}
Variables
# 使用 Argments.js  data, resolver, schema

// query

query($name: String!){
  user(name: $name) {
    id
  }
}

// query variable

{
  "name": "Tim"
}

//result 

 Argments.js 的結果相同

Operation Name 改善 query 的可讀性, 接在 query 後面, 不影響 query 的結果

Operation Name
query WhatIsTimId($name: String!){
  user(name: $name) {
    id
  }
}

Aliases 可以一次 query 多個同 object typefield

query AllUserId($name1: String!, $name2: String!){
  firstUser: user(name: $name1) {
    id
  },
  secondUser: user(name: $name2){
    id
  }
}

// result
{
  "data": {
    "firstUser": {
      "id": "1"
    },
    "secondUser": {
      "id": "2"
    }
  }
}

Fragment: 將 query 片段封裝起來

query AllUserId($name1: String!, $name2: String!){
  firstUser: user(name: $name1) {
    ...userData
  },
  secondUser: user(name: $name2){
    ...userData
  }
}

fragment userData on User{
  id
}

Mutation

// data

const usersData = [
  {
    name: 'Tim',
    id: 1
  },
]

const postsData = [
  {
    authorId: 1,
    title: 'first post'
  }
]

// schema

gql`
  type User {
    name: String
    id: Int
  }

  type Post {
    author: User
    title: String
    content: String
  }
  
  type Query {
    user(name: String!): User
    posts: [Post]
  }
`

// resolver

{
  Query: {
    user: (root, args, context) => {
      const { name } = args;
      return usersData.find((user) => user.name === name)
    }
  },
  Post: {
    author: (parent, args, content) => {
      const { authorId } = parent;
      postsData.push({
        authorId: 1,
        title,
        content
      })
      return postsData[postsData.length - 1]
    }
  }
}

// mutation

mutation updatePosts{
  addPost(title: "mutation is awosome!", content: "hello world"){
    title
    author{
      id
    }
  }
}

// result

{
  "data": {
    "addPost": {
      "title": "mutation is awosome!",
        "author": {
        "id": "1"
      }
    }
  }
}

// query

query AllPosts{
  posts{
    title
  }
}

// result

{
  "data": {
    "posts": [
      {
        "title": "My first post"
      },
      {
        "title": "mutation is awosome!"
      }
    ]
  }
}

input Object Type

// schema

// 注意是使用 input, 而不是一般的 type

input AddPostInput {
  title: String!
  content: String
}

// Mutation 定義

type Mutation {
  addPost(input: AddPostInput): Post
  # 不使用 Object Type 而喂入參數
  # addPost(title:String, content: String): Post
}

// resolver

  Mutation: {
    addPost: (root, args, context) => {
      const { input } = args;
      const { title, content } = input;
      posts.push({
        authorId: 1,
        title,
        content,
      })
      return posts[posts.length - 1]
    }
  }
  
// query

mutation updatePosts($input: AddPostInput) {
  addPost(input: $input) {
    title
    author {
      id
    }
  }
}

// variable

{
  "input":{
    "title": "mutation is awosome!",
    "content": "hello world"
  }
}

Resolver 參數 (parent, args, context)

  1. parent 為此 field 上一層的資料, 也就是該 field 存在於某個物件下, 該物件即為 parent。
  2. args 為 client side 的 query 對此項 fied 傳進來的參數。
  3. context 類似於全域變數的概念,在單一 query 內所有 field 都可享用的資源,通常會放些 user 資料或 ORM 方法。

ref1
ref2

 
12 months ago

Redux middleware 之一, 這讓action creator能夠retrun funcion而不是本來的action object, 這種方式, action creator 成為 thunk(替換程序

換句話說, redux-thunk 能夠讓 action creatordispatch()為參數, 並可以 Async 的 invoke, 這類的 function 就稱為 thunk
redux-thunk lets the action creators invert control by dispatching functions. They would receive dispatch as an argument and may call it asynchronously.

fetch行為來說, 他是一個Async Function(not pure function),若要在redux執行這樣的操作, 就必須把async function 拆成數個 sync function, 如下

step1. 將async action 拆成數個 sync action

action.js
export function startGetAPI() {
  return { type: 'START_GET_API' }
}

export function endGetAPI() {
  return { type: 'END_GET_API' }
}

step2. 在 UI dispatch 這些 action

index.js
store.dispatch(startGetAPI())
const jsonData =  ...// AJAX callback

store.dispatch(endGetAPI(jsonData))

step3. 但是上面這種實作法又會回到 UI 在管理 state的困境, 這時redux thunk可以用 high-order action generator (a function use to create a function)的方式, 來讓 action creator 可以讀懂一系列 async brakedown 的sync function

step4. 以high-order action generator的方式來生成GetAPI()

action.js
export function startGetAPI() {
  return { type: 'START_GET_API' }
}

export function endGetAPI() {
  return { type: 'END_GET_API' }
}

export function getAPI(){
  return (dispatch, state) =>{
    dispatch(startGetAPI())
    const jsonData =  ...// AJAX callback

    dispatch(endGetAPI(jsonData))
  }
}

step5. 在 UI

index.js
import { compose, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
const store = createStore(combineReducers({
  ...
}), compose(applyMiddleware(thunkMiddleware)));

//getAPI() is action function

store.dispatch(getAPI());

實際上的例子

without middle

// action creator

function loadData(dispatch, userId) { // needs to dispatch, so it is first argument

  return fetch(`http://data.com/${userId}`)

    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component

componentWillMount() {
  loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch

}

wtih Thunk Middleware

// action creator

function loadData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these

    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component

componentWillMount() {
  this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do

}

ref

 
12 months ago

What

React 用來取得 dom element的方法, 如同 jQuery 的 $('.class')

When

  1. Managing focus, text selection, or media playback.
  2. Triggering imperative animations.
  3. Integrating with third-party DOM libraries.

How

  1. CallBack Ref
  2. Create Ref(after React v16.3)
//CallBack Ref

import React from 'react'
class MyComponent extends React.component {
  constructor(props) {
    super(props)
    this.myRef = null
    this.setMyRef = domElement => {
      this.myRef = element
    }
  }

  render() {
    return <div ref={this.setMyRef}></div>
  }
}

//Create Ref(after React v16.3)


step1. set ref
import React from 'react'
class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    super(props)
    this.myRef = React.createRef()
  }

  render() {
    return <div ref={this.myRef}></div>
  }
}

step2. get ref
const node = this.myRef.current
(same as document.getElementById('id'))

Ref

 
over 1 year ago

reactotron: A tool inspecting your React JS and React Native apps

install

brew cask install reactotron

Quick Start for React Native

step1. Let's install Reactotron on your project
npm i --save-dev reactotron-react-native

step2. add a file to init reactotro

reactotro.js
import Reactotron from 'reactotron-react-native'

Reactotron
  .configure() // controls connection & communication settings

  .useReactNative() // add all built-in react native plugins

  .connect() // let's connect!

step3. import reactotro in App.js
import './reactotro'

step4. use it
try this one: Reactotron.log('hello rendering world')

  render() {
    Reactotron.log('hello rendering world')
    return (
      ... //some component

    );
  }
 
over 1 year ago

Prepare

git clone https://github.com/eyesofkids/webpack-es6-startkit.git
npm i --save react react-dom
npm i --save react-redux
npm i babel-preset-react --save-dev

.babelrc
{
  "presets": [
    "latest",
    "stage-0",
    "react"
  ],
  "plugins": [
    "transform-flow-strip-types"
  ]
}

step1. build reducer

import { combineReducers } from 'redux'

function items(state = [], action) {
  switch (action.type) {
    case 'ADD_ITEM':
      {
        return [{
          id: action.payload.id,
          text: action.payload.text,
        }, ...state]
      }

    case 'DEL_ITEM':
      {
        return state.filter(item => item.id !== action.id)
      }

    default:
      return state
  }
}

const itemApp = combineReducers({
  items,
})

export default itemApp

step2. build store from reducer

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import itemApp from './reducer'
import MyComponent from './app'

const store = createStore(itemApp,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
ReactDOM.render(
  <Provider store={store}>
    <MyComponent />
  </Provider>, document.getElementById('root')
)  

Provider元件必須為最上(外)層元件

step3. build action object by action creator

action object: 用來描述發生什麼action的object

react-redux中, 只要Action Creator有被呼叫到, 就會自動執行dispatch action來改變state, 而不需額外的使用store.dispatch(action)才能改變state

export const onItemAdd = (payload) => (
  { type: 'ADD_ITEM', payload }
)

export const onItemDel = (id) => (
  { type: 'DEL_ITEM', id }
)

step4. build component

import React from 'react'
import { connect } from 'react-redux'
import * as actionCreators from './action'

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { inputValue: '' }
  }

  handleTextChange = (e) => {
    this.setState({
      inputValue: e.target.value,
    })
  }

  handleItemAdd = () => {
    this.props.onItemAdd({
      id: +new Date(),
      text: this.state.inputValue,
    })

    this.setState({
      inputValue: '',
    })
  }

  render() {
    const { items, onItemDel } = this.props
    console.log(this.props)
    console.log(this.state)
    return (
      <div>
        <div>
          <input
            type="text"
            value={this.state.inputValue}
            onChange={this.handleTextChange}
          />
          <button onClick={this.handleItemAdd}>
            Add Item
          </button>
        </div>
        <p>
          {
            items.map(item => (
              <li key={item.id}>
                <input
                  type="checkbox"
                  id={item.id}
                  onClick={() => onItemDel(item.id)}
                />
                {item.text}
              </li>
            ))
          }
        </p>
      </div>
    )
  }
}

const mapStateToProps = store => (
  { items: store.items }
)

export default connect(mapStateToProps, actionCreators)(MyComponent)
  • mapStateToPropsstore中的statemapping到MyComponentprops
    { items: store.items }左邊的items指的是MyComponent.props.items, 右邊的store.items自然就是store所存的item的state(aka. (store.getState()).items, built by reducer)

  • connect(mapStateToProps, actionCreators)除了上面提到的store中的state mapping component props, actionCreatorscomponent 的 props 擁有

  • onItemAdd

  • onItemDel
    兩個方法(使用this.props.onItemAdd來invoke)

  • items.map(item => ( ...中的itemsconst { items, onItemDel } = this.props解構而來

this.props
{items: Array(0), onItemAdd: ƒ, onItemDel: ƒ}
items:[]
onItemAdd ()
onItemDel ()
__proto__:Object

ref

 
over 1 year ago

step1. import createStore
import { createStore } from 'redux'

step2 . 建立reducer

reducer is a pure function (a function without side effect)
(input oldState and action return newState)

function addItem(state=[], action){
  switch (action.type) {
    case 'ADD_ITEM':
      return [action.text, ...state]
    default:
      return state
  }
}

step3. 建立store

  • store: state的集合
  • state的改變是透過: 事件發生 -> dispatch -> action -> setState (只有setState可以改變state)
createStore(addItem)

step4. 建立render()

state改變的時候render DOM

function render() {
  const items = store.getState().map(itme => (
    (item) ? `<li>${item}</li>`
  ))
}

getState: 取出storestate

step5. subscribe store

store有新狀態的時候呼叫render()

store.subscribe(render)

ref

 
over 1 year ago

Install

https://docs.docker.com/docker-for-mac/install/

Get image

docker pull ubuntu:14.04

Show all images

docker images

Excecute Docker image

sudo docker run -t -i ubuntu:14.04 /bin/bash
-t: 分配一個虛擬終端(pseudo-tty)並綁定到容器的標準輸入
-i: 標準輸入保持打開
-d: 容器啟動後會進入背景執行

Stop Container

docker stop ContainerID

Check Docker Process Status

docker ps

Modify image and save it

  1. pull image
    docker pull training/sinatra

  2. execute image
    docker run -t -i training/sinatra /bin/bash

  3. install gem json
    gem install json

  4. exit container
    exit

  5. save it
    docker commit -m "add json gem" -a "Docker Newbee" origin_image_id Repository_name
    -a: 更新的使用者訊息

Save image to .tar file

docker save -o save_file_name.tar image_id

Remove image

docker rmi image_id

Name container and run it

docker run -d --name web training/webapp

Container Link each other

  1. create new container called db
    sudo docker run -d --name db training/postgres

  2. delete web container
    docker rm -f web

  3. create new container called web
    docker run -d -P --name web --link db:db training/webapp python app.py
    Ref

Stop / remove all Docker containers

docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
  1. how to check docker host ip address

    1. get container docker ps
    2. docker inspect container_id NetworkSettings -> "IPAddress"
  2. check container env variable
    docker exec CONTAINER_ID env

 
over 1 year ago

Demo

Note

nowrap: element in one line, default
wrap: element in multiline

*{
  margin: 0;
  padding: 0;
  box-sizing: border-box
}

.container{
  display: flex;
  flex-wrap: wrap;
}

.block{
  width: 50%;
  height: 50vh;
  border: 1px solid;
}
 
over 1 year ago

Demo

Note

  • display: inline-flex + white-space: nowrap + overflow-x: scroll Demo

white-space: how to handle white-space
nowrap: Text will never wrap to the next line. The text continues on the same line until a
tag is encountered

ref

 
over 1 year ago

Note

  • Capture: Top to Bottom

  • Bubbling: Bottom to Top

divs.forEach(div => div.addEventListener('click', logText, {
    capture: true,  // default false, mean it's bubbling, b to t

    once: ture      // event only fire one time

}))
  • stopPropagation(): stop bubbling