├── .gitignore ├── .plugins └── umi-plugin-autolayout │ ├── .fatherrc.js │ ├── index.js │ ├── lib │ └── runtime.js │ ├── package.json │ └── src │ └── runtime.js ├── README.md ├── dva-model-creator ├── .umirc.ts ├── README.md ├── app.ts ├── mock │ └── todos.js ├── models │ └── todos.ts ├── package.json ├── pages │ ├── index.css │ └── index.tsx ├── services │ └── todos.ts ├── tsconfig.json └── typings.d.ts ├── hox ├── .umirc.js ├── README.md ├── hooks │ └── useCount.js ├── package.json └── pages │ ├── index.css │ ├── index.js │ ├── users.css │ └── users.js ├── icestore ├── README.md ├── mock │ └── todos.js ├── package.json ├── pages │ ├── index.css │ └── index.js └── stores │ ├── index.js │ └── todos.js ├── neeko ├── .umirc.ts ├── README.md ├── mock │ └── todos.ts ├── models │ └── todos.ts ├── package.json ├── pages │ ├── index.css │ ├── index.tsx │ ├── users.css │ └── users.tsx ├── tsconfig.json └── typings.d.ts ├── plugin-model ├── .umirc.ts ├── README.md ├── app.ts ├── mock │ └── todos.js ├── models │ ├── count.ts │ └── todos.ts ├── package.json ├── pages │ ├── index.css │ └── index.tsx ├── services │ └── todos.ts ├── tsconfig.json └── typings.d.ts ├── react-suspense ├── app.js ├── components │ ├── ErrorBoundary.js │ ├── Num.js │ └── Person.js ├── createResource.js ├── package.json ├── pages │ ├── index.css │ └── index.js └── wrapPromise.js ├── swr ├── .umirc.js ├── README.md ├── auth.js ├── fetch.js ├── hooks │ ├── useGlobalState.js │ ├── useTodos.js │ └── useUsers.js ├── layouts │ └── index.js ├── mock │ ├── index.js │ ├── login.js │ ├── todos.js │ └── users.js ├── package.json ├── pages │ ├── global-state.css │ ├── global-state.js │ ├── graphql.js │ ├── index.js │ ├── tab-storage-sync.css │ ├── tab-storage-sync.js │ ├── todos.js │ └── users.js └── storage.js ├── umi-hooks ├── .umirc.js ├── README.md ├── package.json └── pages │ ├── index.css │ ├── index.js │ └── use-async.js ├── unstated-next ├── .umirc.js ├── README.md ├── containers │ └── Count.js ├── layouts │ └── index.js ├── package.json └── pages │ ├── components │ └── CountDisplay.js │ ├── index.css │ ├── index.js │ ├── users.css │ └── users.js └── zustand ├── .umirc.js ├── README.md ├── hooks └── store.js ├── package.json └── pages ├── index.css ├── index.js ├── users.css └── users.js /.gitignore: -------------------------------------------------------------------------------- 1 | /*/node_modules 2 | /*/pages/.umi 3 | /*/yarn.lock 4 | /.plugins/*/node_modules 5 | 6 | -------------------------------------------------------------------------------- /.plugins/umi-plugin-autolayout/.fatherrc.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | target: 'browser', 4 | cjs: 'babel', 5 | } 6 | -------------------------------------------------------------------------------- /.plugins/umi-plugin-autolayout/index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (api) => { 3 | api.addRuntimePlugin(require.resolve('./lib/runtime')); 4 | } 5 | -------------------------------------------------------------------------------- /.plugins/umi-plugin-autolayout/lib/runtime.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.patchRoutes = patchRoutes; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _umi = require("umi"); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 13 | 14 | function patchRoutes(routes) { 15 | var hasLayout = !!routes[0].routes; 16 | var oldRoutes = hasLayout ? routes[0].routes.slice(0) : routes.slice(0); 17 | 18 | function Layout(props) { 19 | var routes = oldRoutes.filter(function (r) { 20 | return r.path && !r.path.startsWith('/components'); 21 | }); 22 | return _react.default.createElement("div", null, _react.default.createElement("h1", null, "Navigation"), _react.default.createElement("ul", null, routes.map(function (r) { 23 | return _react.default.createElement("li", { 24 | key: r.path 25 | }, _react.default.createElement(_umi.Link, { 26 | to: r.path 27 | }, r.path === '/' ? 'home' : r.path.slice(1))); 28 | })), _react.default.createElement("button", { 29 | onClick: _umi.router.goBack 30 | }, "Go Back"), props.children); 31 | } 32 | 33 | if (hasLayout) { 34 | routes[0].routes = [{ 35 | path: '/', 36 | component: Layout, 37 | routes: oldRoutes 38 | }]; 39 | } else { 40 | routes.splice(0, oldRoutes.length, { 41 | path: '/', 42 | component: Layout, 43 | routes: oldRoutes 44 | }); 45 | } 46 | } -------------------------------------------------------------------------------- /.plugins/umi-plugin-autolayout/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "umi-plugin-autolayout", 3 | "version": "0.0.1-alpha.1" 4 | } 5 | -------------------------------------------------------------------------------- /.plugins/umi-plugin-autolayout/src/runtime.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, router } from 'umi'; 3 | 4 | export function patchRoutes(routes) { 5 | const hasLayout = !!routes[0].routes; 6 | const oldRoutes = hasLayout ? routes[0].routes.slice(0) : routes.slice(0); 7 | 8 | function Layout(props) { 9 | const routes = oldRoutes.filter(r => r.path && !r.path.startsWith('/components')); 10 | return ( 11 |
12 |

Navigation

13 | 20 | 21 | { 22 | props.children 23 | } 24 |
25 | ); 26 | } 27 | 28 | if (hasLayout) { 29 | routes[0].routes = [ 30 | { 31 | path: '/', 32 | component: Layout, 33 | routes: oldRoutes, 34 | }, 35 | ]; 36 | } else { 37 | routes.splice(0, oldRoutes.length, { 38 | path: '/', 39 | component: Layout, 40 | routes: oldRoutes, 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learn React with Umi 2 | 3 | ## Topics 4 | 5 | * [dva-model-creator](./dva-model-creator) 6 | * [hox](./hox) 7 | * [icestore](./icestore) 8 | * [neeko](./neeko) 9 | * [@umijs/plugin-model](./plugin-model) 10 | * [react-suspense](./react-suspense) 11 | * [swr](./swr) 12 | * [@umijs/hooks](./umi-hooks) 13 | * [unstated-next](./unstated-next) 14 | * [zustand](./zustand) 15 | 16 | ## Usage 17 | 18 | Install umi globally. 19 | 20 | ```bash 21 | $ yarn global add umi 22 | ``` 23 | 24 | Run example, e.g. swr, 25 | 26 | ```bash 27 | $ cd swr 28 | $ yarn 29 | $ umi dev 30 | ``` 31 | 32 | ## LICENSE 33 | 34 | MIT 35 | -------------------------------------------------------------------------------- /dva-model-creator/.umirc.ts: -------------------------------------------------------------------------------- 1 | import { IConfig } from 'umi-types'; 2 | 3 | export default { 4 | plugins: [ 5 | [ 6 | 'umi-plugin-react', 7 | { 8 | dva: true, 9 | }, 10 | ], 11 | 'umi-plugin-autolayout', 12 | ], 13 | } as IConfig; 14 | -------------------------------------------------------------------------------- /dva-model-creator/README.md: -------------------------------------------------------------------------------- 1 | # dva-model-creator 2 | 3 | ## Links 4 | 5 | * https://github.com/DiamondYuan/dva-model-creator 6 | * https://github.com/HoiShan/umi-practice 7 | * https://github.com/webclipper/web-clipper 8 | 9 | -------------------------------------------------------------------------------- /dva-model-creator/app.ts: -------------------------------------------------------------------------------- 1 | 2 | export const dva = { 3 | config: { 4 | namespacePrefixWarning: false, 5 | onError(err: ErrorEvent) { 6 | err.preventDefault(); 7 | console.error(err.message); 8 | }, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /dva-model-creator/mock/todos.js: -------------------------------------------------------------------------------- 1 | 2 | const todos = [ 3 | { name: 'react' }, 4 | { name: 'vue', done: true }, 5 | { name: 'angular' }, 6 | ]; 7 | 8 | export default { 9 | '/api/todos': todos, 10 | '/api/todos/add': (req, res) => { 11 | todos.push({ name: req.query.text }); 12 | return res.json({ success: 1 }); 13 | }, 14 | '/api/todos/clear': (req, res) => { 15 | todos.splice(2, todos.length); 16 | return res.json({ success: 1 }); 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /dva-model-creator/models/todos.ts: -------------------------------------------------------------------------------- 1 | import { actionCreatorFactory, DvaModelBuilder } from 'dva-model-creator'; 2 | import * as services from '../services/todos'; 3 | 4 | interface Todo { 5 | name: string; 6 | done?: boolean; 7 | } 8 | 9 | export interface Todos { 10 | data: Todo[]; 11 | } 12 | 13 | const namespace = 'todos'; 14 | 15 | const actionCreator = actionCreatorFactory(namespace); 16 | export const saveTodos = actionCreator('saveTodos'); 17 | export const addTodo = actionCreator('addTodo'); 18 | export const fetchTodos = actionCreator('fetchTodos'); 19 | 20 | const model = new DvaModelBuilder({ data: [] }, namespace) 21 | .subscript(({ dispatch }) => { 22 | dispatch(fetchTodos()); 23 | }) 24 | .case(saveTodos, (state, payload) => { 25 | return { 26 | data: payload, 27 | }; 28 | }) 29 | .takeEvery(addTodo, function *(payload, { call, put }) { 30 | yield call(services.addTodo, payload); 31 | yield put(fetchTodos); 32 | }) 33 | .takeEvery(fetchTodos, function *(payload, { call, put }) { 34 | const todos = yield call(services.fetchTodo); 35 | yield put(saveTodos(todos)); 36 | }) 37 | .build(); 38 | 39 | export default model; 40 | -------------------------------------------------------------------------------- /dva-model-creator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "dva-model-creator": "^0.4.3", 4 | "react": "^16.11.0" 5 | }, 6 | "devDependencies": { 7 | "@types/react": "^16.9.11", 8 | "umi-plugin-react": "^1.12.8", 9 | "umi-types": "^0.5.3", 10 | "umi-plugin-autolayout": "link:../.plugins/umi-plugin-autolayout" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /dva-model-creator/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #F2C679; 4 | } 5 | -------------------------------------------------------------------------------- /dva-model-creator/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { connect } from 'dva'; 3 | import { Todos, fetchTodos, addTodo } from '../models/todos'; 4 | import styles from './index.css'; 5 | 6 | function mapStateToProps(state: { todos: Todos }) { 7 | return { 8 | todos: state.todos, 9 | }; 10 | } 11 | 12 | export default connect(mapStateToProps)(function(props) { 13 | const [value, setValue] = useState(''); 14 | return ( 15 |
16 |

Page index

17 |

Todos

18 | { 19 | (props.todos as Todos).data.map(({ name, done }) => { 20 | return
  • { name }
  • ; 21 | }) 22 | } 23 |
    { 24 | ev.preventDefault(); 25 | await props.dispatch(addTodo(value)); 26 | setValue(''); 27 | await props.dispatch(fetchTodos()); 28 | }}> 29 | setValue(ev.target.value)} /> 30 |
    31 |
    32 | ); 33 | }) 34 | -------------------------------------------------------------------------------- /dva-model-creator/services/todos.ts: -------------------------------------------------------------------------------- 1 | 2 | export async function addTodo(text: string) { 3 | await fetch(`/api/todos/add?text=${text}`); 4 | } 5 | 6 | export async function fetchTodo() { 7 | const res = await fetch(`/api/todos`); 8 | const ret = await res.json(); 9 | return ret; 10 | } 11 | -------------------------------------------------------------------------------- /dva-model-creator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build/dist", 4 | "module": "esnext", 5 | "target": "esnext", 6 | "lib": ["esnext", "dom"], 7 | "sourceMap": true, 8 | "baseUrl": ".", 9 | "jsx": "react", 10 | "allowSyntheticDefaultImports": true, 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "noImplicitReturns": true, 14 | "suppressImplicitAnyIndexErrors": true, 15 | "noUnusedLocals": true, 16 | "allowJs": true, 17 | "experimentalDecorators": true, 18 | "strict": true, 19 | "paths": { 20 | "@/*": ["./src/*"] 21 | } 22 | }, 23 | "exclude": [ 24 | "node_modules", 25 | "build", 26 | "scripts", 27 | "acceptance-tests", 28 | "webpack", 29 | "jest", 30 | "src/setupTests.ts", 31 | "tslint:latest", 32 | "tslint-config-prettier" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /dva-model-creator/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | -------------------------------------------------------------------------------- /hox/.umirc.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | plugins: [ 4 | 'umi-plugin-autolayout', 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /hox/README.md: -------------------------------------------------------------------------------- 1 | # hox 2 | 3 | ## Links 4 | 5 | * https://github.com/umijs/hox 6 | * https://zhuanlan.zhihu.com/p/88738712 7 | -------------------------------------------------------------------------------- /hox/hooks/useCount.js: -------------------------------------------------------------------------------- 1 | import { createModel } from "hox"; 2 | import { useState } from 'react'; 3 | 4 | function useCount() { 5 | const [count, setState] = useState(0); 6 | 7 | function increment() { 8 | setState(count + 1); 9 | } 10 | 11 | return { 12 | count, 13 | increment, 14 | }; 15 | } 16 | 17 | export default createModel(useCount); 18 | export { useCount as useLocalCount }; 19 | -------------------------------------------------------------------------------- /hox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "hox": "^1.0.0" 4 | }, 5 | "devDependencies": { 6 | "umi-plugin-autolayout": "link:../.plugins/umi-plugin-autolayout" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /hox/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #F2CC79; 4 | } 5 | -------------------------------------------------------------------------------- /hox/pages/index.js: -------------------------------------------------------------------------------- 1 | import useCount from "../hooks/useCount"; 2 | import styles from './index.css'; 3 | 4 | export default function() { 5 | const { count, increment } = useCount(); 6 | return ( 7 |
    8 |

    Index Page (Global Hook)

    9 |

    Count: {count}

    10 | 11 |
    12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /hox/pages/users.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #7993F2; 4 | } 5 | -------------------------------------------------------------------------------- /hox/pages/users.js: -------------------------------------------------------------------------------- 1 | 2 | import styles from './users.css'; 3 | import { useLocalCount } from "../hooks/useCount"; 4 | 5 | export default function() { 6 | const { count, increment } = useLocalCount(); 7 | return ( 8 |
    9 |

    Users Page (Local Hook)

    10 |

    Count: {count}

    11 | 12 |
    13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /icestore/README.md: -------------------------------------------------------------------------------- 1 | # icestore 2 | 3 | ## Links 4 | 5 | * https://github.com/ice-lab/icestore 6 | -------------------------------------------------------------------------------- /icestore/mock/todos.js: -------------------------------------------------------------------------------- 1 | 2 | const todos = [ 3 | { name: 'react' }, 4 | { name: 'vue', done: true }, 5 | { name: 'angular' }, 6 | ]; 7 | 8 | export default { 9 | '/api/todos': todos, 10 | '/api/todos/add': (req, res) => { 11 | todos.push({ name: req.query.text }); 12 | return res.json({ success: 1 }); 13 | }, 14 | '/api/todos/clear': (req, res) => { 15 | todos.splice(2, todos.length); 16 | return res.json({ success: 1 }); 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /icestore/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@ice/store": "^0.3.0", 4 | "@ice/store-logger": "^0.1.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /icestore/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #7983F2; 4 | } 5 | -------------------------------------------------------------------------------- /icestore/pages/index.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import stores from '../stores'; 3 | import styles from './index.css'; 4 | 5 | export default function() { 6 | const { 7 | todos, 8 | addTodo, 9 | fetchTodos, 10 | } = stores.useStore('todos'); 11 | const [value, setValue] = useState(''); 12 | 13 | useEffect(() => { 14 | fetchTodos(); 15 | }, []); 16 | 17 | return ( 18 |
    19 |

    Page index

    20 |

    todos

    21 |
      22 | { 23 | todos.map(({ name, done }) => { 24 | return
    • { name } {done ? 'x' : ''}
    • 25 | }) 26 | } 27 |
    28 |
    { 29 | e.preventDefault(); 30 | await addTodo(value); 31 | setValue(''); 32 | await fetchTodos(); 33 | }}> 34 | setValue(e.target.value)} /> 35 |
    36 |
    37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /icestore/stores/index.js: -------------------------------------------------------------------------------- 1 | import Icestore from '@ice/store'; 2 | import logger from '@ice/store-logger'; 3 | import todos from './todos'; 4 | 5 | const stores = new Icestore(); 6 | stores.applyMiddleware([ 7 | logger, 8 | ]); 9 | stores.registerStore('todos', todos); 10 | 11 | export default stores; 12 | -------------------------------------------------------------------------------- /icestore/stores/todos.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | todos: [ 4 | ], 5 | async fetchTodos() { 6 | const todos = await fetch(`/api/todos`) 7 | .then(x => x.json()); 8 | this.todos = todos; 9 | }, 10 | async addTodo(todo) { 11 | await fetch(`/api/todos/add?text=${todo}`); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /neeko/.umirc.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: ['umi-plugin-autolayout'], 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /neeko/README.md: -------------------------------------------------------------------------------- 1 | # neeko(阿里内部) 2 | 3 | ## Links 4 | 5 | * https://yuque.antfin-inc.com/kot/neeko 6 | -------------------------------------------------------------------------------- /neeko/mock/todos.ts: -------------------------------------------------------------------------------- 1 | 2 | const todos = [ 3 | { name: 'react' }, 4 | { name: 'vue', done: true }, 5 | { name: 'angular' }, 6 | ]; 7 | 8 | export default { 9 | '/api/todos': todos, 10 | '/api/todos/add': (req, res) => { 11 | todos.push({ name: req.query.text }); 12 | return res.json({ success: 1 }); 13 | }, 14 | '/api/todos/clear': (req, res) => { 15 | todos.splice(2, todos.length); 16 | return res.json({ success: 1 }); 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /neeko/models/todos.ts: -------------------------------------------------------------------------------- 1 | import { model } from '@alipay/xmas-neeko'; 2 | 3 | interface ITodo { 4 | name: string; 5 | } 6 | 7 | export default model({ 8 | state: { 9 | todos: [] as ITodo[], 10 | }, 11 | computed: { 12 | length(): number { 13 | return this.todos.length; 14 | }, 15 | }, 16 | effects: { 17 | async addTodo(name: string) { 18 | await fetch(`/api/todos/add?text=${name}`); 19 | }, 20 | async fetchTodos() { 21 | const res = await fetch(`/api/todos`); 22 | const todos = await res.json(); 23 | this.update({ 24 | todos, 25 | }); 26 | }, 27 | }, 28 | hooks: { 29 | init() { 30 | this.fetchTodos(); 31 | }, 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /neeko/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@alipay/xmas-neeko": "^0.3.3" 4 | }, 5 | "devDependencies": { 6 | "@types/react": "^16.9.11", 7 | "react": "^16.11.0", 8 | "umi-plugin-autolayout": "link:../.plugins/umi-plugin-autolayout" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /neeko/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #F279E0; 4 | } 5 | -------------------------------------------------------------------------------- /neeko/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { observer } from '@alipay/xmas-neeko/react'; 3 | import styles from './index.css'; 4 | import todos from '../models/todos'; 5 | 6 | export default observer(function() { 7 | const [value, setValue] = useState(''); 8 | 9 | return ( 10 |
    11 |

    Page index

    12 |
    Totally {todos.length} todos.
    13 | { 14 | todos.todos.map(todo =>
  • { todo.name }
  • ) 15 | } 16 |
    { 17 | ev.preventDefault(); 18 | await todos.addTodo(value); 19 | setValue(''); 20 | await todos.fetchTodos(); 21 | }}> 22 | setValue(ev.target.value)} /> 23 |
    24 |
    25 | ); 26 | }) 27 | -------------------------------------------------------------------------------- /neeko/pages/users.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #C079F2; 4 | } 5 | -------------------------------------------------------------------------------- /neeko/pages/users.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | 4 | import styles from './users.css'; 5 | 6 | export default function() { 7 | return ( 8 |
    9 |

    Page users

    10 |
    11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /neeko/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build/dist", 4 | "module": "esnext", 5 | "target": "esnext", 6 | "lib": ["esnext", "dom"], 7 | "sourceMap": true, 8 | "baseUrl": ".", 9 | "jsx": "react", 10 | "allowSyntheticDefaultImports": true, 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "noImplicitReturns": true, 14 | "suppressImplicitAnyIndexErrors": true, 15 | "noUnusedLocals": true, 16 | "allowJs": true, 17 | "experimentalDecorators": true, 18 | "strict": true, 19 | "paths": { 20 | "@/*": ["./src/*"] 21 | } 22 | }, 23 | "exclude": [ 24 | "node_modules", 25 | "build", 26 | "scripts", 27 | "acceptance-tests", 28 | "webpack", 29 | "jest", 30 | "src/setupTests.ts", 31 | "tslint:latest", 32 | "tslint-config-prettier" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /neeko/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | -------------------------------------------------------------------------------- /plugin-model/.umirc.ts: -------------------------------------------------------------------------------- 1 | import { IConfig } from 'umi-types'; 2 | 3 | export default { 4 | plugins: [ 5 | 'umi-plugin-autolayout', 6 | '@umijs/plugin-model', 7 | '@umijs/plugin-initial-state', 8 | ], 9 | } as IConfig; 10 | -------------------------------------------------------------------------------- /plugin-model/README.md: -------------------------------------------------------------------------------- 1 | # @umijs/plugin-model 2 | 3 | ## Links 4 | 5 | * https://github.com/umijs/plugin-model 6 | -------------------------------------------------------------------------------- /plugin-model/app.ts: -------------------------------------------------------------------------------- 1 | 2 | export async function getInitialState() { 3 | return 'Hello world'; 4 | } 5 | -------------------------------------------------------------------------------- /plugin-model/mock/todos.js: -------------------------------------------------------------------------------- 1 | 2 | const todos = [ 3 | { name: 'react' }, 4 | { name: 'vue', done: true }, 5 | { name: 'angular' }, 6 | ]; 7 | 8 | export default { 9 | '/api/todos': todos, 10 | '/api/todos/add': (req, res) => { 11 | todos.push({ name: req.query.text }); 12 | return res.json({ success: 1 }); 13 | }, 14 | '/api/todos/clear': (req, res) => { 15 | todos.splice(2, todos.length); 16 | return res.json({ success: 1 }); 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /plugin-model/models/count.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { useModel } from 'umi'; 3 | 4 | export default function () { 5 | const [ count, setCount ] = useState(0); 6 | const { todos } = useModel('todos'); 7 | 8 | useEffect(() => { 9 | setCount(todos.length); 10 | }, [todos]); 11 | 12 | return { 13 | count, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /plugin-model/models/todos.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import * as todoService from '@/services/todos'; 3 | 4 | export default function () { 5 | const [todos, setTodos] = useState([]); 6 | 7 | async function fetchTodos() { 8 | const todos = await todoService.fetchTodo(); 9 | setTodos(todos); 10 | } 11 | 12 | async function addTodo(text: string) { 13 | await todoService.addTodo(text); 14 | await fetchTodos(); 15 | } 16 | 17 | useEffect(() => { 18 | (async () => { 19 | await fetchTodos(); 20 | })(); 21 | }, []); 22 | 23 | return { 24 | todos, 25 | addTodo, 26 | fetchTodos, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugin-model/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "umi dev" 4 | }, 5 | "devDependencies": { 6 | "@types/react": "^16.9.11", 7 | "@umijs/plugin-model": "^0.1.0", 8 | "@umijs/plugin-initial-state": "^0.1.0", 9 | "umi-types": "^0.5.3", 10 | "umi-plugin-autolayout": "link:../.plugins/umi-plugin-autolayout" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /plugin-model/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #79CCF2; 4 | } 5 | -------------------------------------------------------------------------------- /plugin-model/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useModel } from 'umi'; 3 | import styles from './index.css'; 4 | 5 | export default function() { 6 | const [value, setValue] = useState(''); 7 | const { todos, addTodo, fetchTodos } = useModel('todos'); 8 | const { count } = useModel('count'); 9 | const { initialState } = useModel('@@initialState'); 10 | return ( 11 |
    12 |

    Page index

    13 |

    Test {initialState || ''}

    14 |

    Todos ({count})

    15 | { 16 | todos.map(({ name, done }) => { 17 | return
  • { name }
  • ; 18 | }) 19 | } 20 |
    { 21 | ev.preventDefault(); 22 | await addTodo(value); 23 | setValue(''); 24 | await fetchTodos(); 25 | }}> 26 | setValue(ev.target.value)} /> 27 |
    28 |
    29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /plugin-model/services/todos.ts: -------------------------------------------------------------------------------- 1 | 2 | export async function addTodo(text: string) { 3 | await fetch(`/api/todos/add?text=${text}`); 4 | } 5 | 6 | export async function fetchTodo() { 7 | const res = await fetch(`/api/todos`); 8 | const ret = await res.json(); 9 | return ret; 10 | } 11 | -------------------------------------------------------------------------------- /plugin-model/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build/dist", 4 | "module": "esnext", 5 | "target": "esnext", 6 | "lib": ["esnext", "dom"], 7 | "sourceMap": true, 8 | "baseUrl": ".", 9 | "jsx": "react", 10 | "allowSyntheticDefaultImports": true, 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "noImplicitReturns": true, 14 | "suppressImplicitAnyIndexErrors": true, 15 | "noUnusedLocals": true, 16 | "allowJs": true, 17 | "experimentalDecorators": true, 18 | "strict": true, 19 | "paths": { 20 | "@/*": ["./*"], 21 | "@@/*": ["./pages/.umi/*"] 22 | } 23 | }, 24 | "exclude": [ 25 | "node_modules", 26 | "build", 27 | "scripts", 28 | "acceptance-tests", 29 | "webpack", 30 | "jest", 31 | "src/setupTests.ts", 32 | "tslint:latest", 33 | "tslint-config-prettier" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /plugin-model/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | -------------------------------------------------------------------------------- /react-suspense/app.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import React from 'react'; 3 | 4 | export function render() { 5 | ReactDOM.createRoot(document.getElementById('root')).render( 6 | React.createElement(require('@tmp/router').default), 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /react-suspense/components/ErrorBoundary.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class ErrorBoundary extends React.Component { 4 | state = { 5 | hasError: false, error: null 6 | } 7 | 8 | static getDerivedStateFromError(error) { 9 | return { 10 | hasError: true, 11 | error, 12 | }; 13 | } 14 | 15 | render() { 16 | if (this.state.hasError) { 17 | return

    Something error: {this.state.error.message}

    18 | } 19 | 20 | return this.props.children; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /react-suspense/components/Num.js: -------------------------------------------------------------------------------- 1 | 2 | export default function Num(props) { 3 | const num = props.resource.num.read(); 4 | return ( 5 |
    6 | Number: {num} 7 |
    8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /react-suspense/components/Person.js: -------------------------------------------------------------------------------- 1 | 2 | export default function Person(props) { 3 | const person = props.resource.person.read(); 4 | return ( 5 |
    6 | name: {person.name.first} 7 |
    8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /react-suspense/createResource.js: -------------------------------------------------------------------------------- 1 | import wrapPromise from "./wrapPromise"; 2 | 3 | function fetchPerson() { 4 | return fetch('https://randomuser.me/api') 5 | .then(x => x.json()) 6 | .then(x => x.results[0]); 7 | } 8 | 9 | function getNum() { 10 | return new Promise(resolve => { 11 | setTimeout(() => { 12 | resolve(Math.random()); 13 | }, 1000); 14 | }); 15 | } 16 | 17 | export default function createResource() { 18 | return { 19 | person: wrapPromise(fetchPerson()), 20 | num: wrapPromise(getNum()), 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /react-suspense/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "react": "^0.0.0-experimental-f6b8d31a7", 4 | "react-dom": "^0.0.0-experimental-f6b8d31a7" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /react-suspense/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #797BF2; 4 | } 5 | -------------------------------------------------------------------------------- /react-suspense/pages/index.js: -------------------------------------------------------------------------------- 1 | import { Suspense, useState, useTransition } from 'react'; 2 | import styles from './index.css'; 3 | import createResource from "../createResource"; 4 | import Person from "../components/Person"; 5 | import Num from "../components/Num"; 6 | import ErrorBoundary from "../components/ErrorBoundary"; 7 | 8 | const initialResource = createResource(); 9 | 10 | export default function() { 11 | const [resource, setResource] = useState(initialResource); 12 | const [startTransition, isPending] = useTransition({ 13 | timeoutMs: 1000, 14 | }); 15 | 16 | return ( 17 |
    18 |

    Page index

    19 | 20 | loading...}> 21 | 22 | 23 | loading...}> 24 | 25 | 26 | 27 | 32 |
    33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /react-suspense/wrapPromise.js: -------------------------------------------------------------------------------- 1 | 2 | export default function wrapPromise(promise) { 3 | let status = 'pending'; 4 | let result = ''; 5 | let suspender = promise.then(r => { 6 | status = 'success'; 7 | result = r; 8 | }, e => { 9 | status = 'error'; 10 | result = e; 11 | }); 12 | 13 | return { 14 | read() { 15 | if (status === 'pending') { 16 | throw suspender; 17 | } else if (status === 'error') { 18 | throw result; 19 | } 20 | return result; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /swr/.umirc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: ['umi-plugin-autolayout'], 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /swr/README.md: -------------------------------------------------------------------------------- 1 | # swr 2 | 3 | ## Links 4 | 5 | * https://swr.now.sh/ 6 | * https://github.com/zeit/swr 7 | 8 | -------------------------------------------------------------------------------- /swr/auth.js: -------------------------------------------------------------------------------- 1 | 2 | export function login() { 3 | document.cookie = 'swr-test-token=swr;' 4 | } 5 | 6 | export function logout() { 7 | document.cookie = 'swr-test-token=; expires=Thu, 01 Jan 1970 00:00:01 GMT;' 8 | } 9 | -------------------------------------------------------------------------------- /swr/fetch.js: -------------------------------------------------------------------------------- 1 | 2 | export default async (...args) => { 3 | const res = await fetch(...args); 4 | return await res.json(); 5 | } 6 | -------------------------------------------------------------------------------- /swr/hooks/useGlobalState.js: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr'; 2 | 3 | let state = { 4 | name: 'sorrycc', 5 | }; 6 | 7 | export default function () { 8 | const { data, revalidate } = useSWR('globalState', () => Promise.resolve(state), { 9 | revalidateOnFocus: false, 10 | }); 11 | return { 12 | data, 13 | setData(newData) { 14 | state = { 15 | ...data, 16 | ...newData, 17 | }; 18 | revalidate(); 19 | }, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /swr/hooks/useTodos.js: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr'; 2 | import fetch from '../fetch'; 3 | 4 | export default function () { 5 | return useSWR(`/api/todos`, fetch); 6 | } 7 | -------------------------------------------------------------------------------- /swr/hooks/useUsers.js: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr'; 2 | import { useState } from 'react'; 3 | import fetch from '../fetch'; 4 | 5 | export default function (initPage) { 6 | const [page, setPage] = useState(initPage); 7 | const { data } = useSWR(`/api/users?page=${page}`, fetch, { 8 | revalidateOnFocus: false, 9 | }); 10 | return { 11 | data, 12 | page, 13 | setPage, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /swr/layouts/index.js: -------------------------------------------------------------------------------- 1 | import useSWR from "swr"; 2 | import { Link, router } from 'umi'; 3 | import fetch from '../fetch'; 4 | import {login, logout} from "../auth"; 5 | 6 | export default function (props) { 7 | const { data, revalidate } = useSWR('/api/login', fetch); 8 | if (!data) return

    loading...

    ; 9 | if (data.loggedIn) { 10 | return ( 11 |
    12 |

    Welcome, { data.name }

    13 | 14 | 18 | { props.children } 19 |
    20 | ); 21 | } else { 22 | return ( 23 |
    24 |

    Please login

    25 | 29 |
    30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /swr/mock/index.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | '/api/readers': (req, res) => { 4 | setTimeout(() => { 5 | res.json([ 6 | { id: 1, name: 'sorrycc', }, 7 | { id: 2, name: 'pigcan', }, 8 | ]); 9 | }, 1000); 10 | }, 11 | '/api/status': (req, res) => { 12 | setTimeout(() => { 13 | res.json({ 14 | messageCount: 5, 15 | }); 16 | }, 2000); 17 | }, 18 | '/api/count': (req, res) => { 19 | setTimeout(() => { 20 | res.json({ 21 | count: parseInt(req.query.count, 10) + 5, 22 | }); 23 | }, 1000); 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /swr/mock/login.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | '/api/login': (req, res) => { 4 | // ref: http://github.com/zeit/swr/tree/master/examples/focus-revalidate/pages/api/user.js 5 | if (req.headers.cookie && req.headers.cookie.includes('swr-test-token=swr')) { 6 | // authorized 7 | res.json({ 8 | loggedIn: true, 9 | name: 'ChenCheng', 10 | avatar: 'https://github.com/sorrycc.png' 11 | }) 12 | return 13 | } 14 | res.json({ 15 | loggedIn: false 16 | }) 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /swr/mock/todos.js: -------------------------------------------------------------------------------- 1 | 2 | const todos = [ 3 | 'Learn swr', 4 | 'Buy Airpod Pro', 5 | ]; 6 | 7 | export default { 8 | '/api/todos': todos, 9 | '/api/todos/add': (req, res) => { 10 | todos.push(req.query.text); 11 | return res.json({ success: 1 }); 12 | }, 13 | '/api/todos/clear': (req, res) => { 14 | todos.splice(2, todos.length); 15 | return res.json({ success: 1 }); 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /swr/mock/users.js: -------------------------------------------------------------------------------- 1 | 2 | const users = [ 3 | { 4 | "id": 1, 5 | "name": "Leanne Graham", 6 | "username": "Bret", 7 | "email": "Sincere@april.biz", 8 | "address": { 9 | "street": "Kulas Light", 10 | "suite": "Apt. 556", 11 | "city": "Gwenborough", 12 | "zipcode": "92998-3874", 13 | "geo": { 14 | "lat": "-37.3159", 15 | "lng": "81.1496" 16 | } 17 | }, 18 | "phone": "1-770-736-8031 x56442", 19 | "website": "hildegard.org", 20 | "company": { 21 | "name": "Romaguera-Crona", 22 | "catchPhrase": "Multi-layered client-server neural-net", 23 | "bs": "harness real-time e-markets" 24 | } 25 | }, 26 | { 27 | "id": 2, 28 | "name": "Ervin Howell", 29 | "username": "Antonette", 30 | "email": "Shanna@melissa.tv", 31 | "address": { 32 | "street": "Victor Plains", 33 | "suite": "Suite 879", 34 | "city": "Wisokyburgh", 35 | "zipcode": "90566-7771", 36 | "geo": { 37 | "lat": "-43.9509", 38 | "lng": "-34.4618" 39 | } 40 | }, 41 | "phone": "010-692-6593 x09125", 42 | "website": "anastasia.net", 43 | "company": { 44 | "name": "Deckow-Crist", 45 | "catchPhrase": "Proactive didactic contingency", 46 | "bs": "synergize scalable supply-chains" 47 | } 48 | }, 49 | { 50 | "id": 3, 51 | "name": "Clementine Bauch", 52 | "username": "Samantha", 53 | "email": "Nathan@yesenia.net", 54 | "address": { 55 | "street": "Douglas Extension", 56 | "suite": "Suite 847", 57 | "city": "McKenziehaven", 58 | "zipcode": "59590-4157", 59 | "geo": { 60 | "lat": "-68.6102", 61 | "lng": "-47.0653" 62 | } 63 | }, 64 | "phone": "1-463-123-4447", 65 | "website": "ramiro.info", 66 | "company": { 67 | "name": "Romaguera-Jacobson", 68 | "catchPhrase": "Face to face bifurcated interface", 69 | "bs": "e-enable strategic applications" 70 | } 71 | }, 72 | { 73 | "id": 4, 74 | "name": "Patricia Lebsack", 75 | "username": "Karianne", 76 | "email": "Julianne.OConner@kory.org", 77 | "address": { 78 | "street": "Hoeger Mall", 79 | "suite": "Apt. 692", 80 | "city": "South Elvis", 81 | "zipcode": "53919-4257", 82 | "geo": { 83 | "lat": "29.4572", 84 | "lng": "-164.2990" 85 | } 86 | }, 87 | "phone": "493-170-9623 x156", 88 | "website": "kale.biz", 89 | "company": { 90 | "name": "Robel-Corkery", 91 | "catchPhrase": "Multi-tiered zero tolerance productivity", 92 | "bs": "transition cutting-edge web services" 93 | } 94 | }, 95 | { 96 | "id": 5, 97 | "name": "Chelsey Dietrich", 98 | "username": "Kamren", 99 | "email": "Lucio_Hettinger@annie.ca", 100 | "address": { 101 | "street": "Skiles Walks", 102 | "suite": "Suite 351", 103 | "city": "Roscoeview", 104 | "zipcode": "33263", 105 | "geo": { 106 | "lat": "-31.8129", 107 | "lng": "62.5342" 108 | } 109 | }, 110 | "phone": "(254)954-1289", 111 | "website": "demarco.info", 112 | "company": { 113 | "name": "Keebler LLC", 114 | "catchPhrase": "User-centric fault-tolerant solution", 115 | "bs": "revolutionize end-to-end systems" 116 | } 117 | }, 118 | { 119 | "id": 6, 120 | "name": "Mrs. Dennis Schulist", 121 | "username": "Leopoldo_Corkery", 122 | "email": "Karley_Dach@jasper.info", 123 | "address": { 124 | "street": "Norberto Crossing", 125 | "suite": "Apt. 950", 126 | "city": "South Christy", 127 | "zipcode": "23505-1337", 128 | "geo": { 129 | "lat": "-71.4197", 130 | "lng": "71.7478" 131 | } 132 | }, 133 | "phone": "1-477-935-8478 x6430", 134 | "website": "ola.org", 135 | "company": { 136 | "name": "Considine-Lockman", 137 | "catchPhrase": "Synchronised bottom-line interface", 138 | "bs": "e-enable innovative applications" 139 | } 140 | }, 141 | { 142 | "id": 7, 143 | "name": "Kurtis Weissnat", 144 | "username": "Elwyn.Skiles", 145 | "email": "Telly.Hoeger@billy.biz", 146 | "address": { 147 | "street": "Rex Trail", 148 | "suite": "Suite 280", 149 | "city": "Howemouth", 150 | "zipcode": "58804-1099", 151 | "geo": { 152 | "lat": "24.8918", 153 | "lng": "21.8984" 154 | } 155 | }, 156 | "phone": "210.067.6132", 157 | "website": "elvis.io", 158 | "company": { 159 | "name": "Johns Group", 160 | "catchPhrase": "Configurable multimedia task-force", 161 | "bs": "generate enterprise e-tailers" 162 | } 163 | }, 164 | { 165 | "id": 8, 166 | "name": "Nicholas Runolfsdottir V", 167 | "username": "Maxime_Nienow", 168 | "email": "Sherwood@rosamond.me", 169 | "address": { 170 | "street": "Ellsworth Summit", 171 | "suite": "Suite 729", 172 | "city": "Aliyaview", 173 | "zipcode": "45169", 174 | "geo": { 175 | "lat": "-14.3990", 176 | "lng": "-120.7677" 177 | } 178 | }, 179 | "phone": "586.493.6943 x140", 180 | "website": "jacynthe.com", 181 | "company": { 182 | "name": "Abernathy Group", 183 | "catchPhrase": "Implemented secondary concept", 184 | "bs": "e-enable extensible e-tailers" 185 | } 186 | }, 187 | { 188 | "id": 9, 189 | "name": "Glenna Reichert", 190 | "username": "Delphine", 191 | "email": "Chaim_McDermott@dana.io", 192 | "address": { 193 | "street": "Dayna Park", 194 | "suite": "Suite 449", 195 | "city": "Bartholomebury", 196 | "zipcode": "76495-3109", 197 | "geo": { 198 | "lat": "24.6463", 199 | "lng": "-168.8889" 200 | } 201 | }, 202 | "phone": "(775)976-6794 x41206", 203 | "website": "conrad.com", 204 | "company": { 205 | "name": "Yost and Sons", 206 | "catchPhrase": "Switchable contextually-based project", 207 | "bs": "aggregate real-time technologies" 208 | } 209 | }, 210 | { 211 | "id": 10, 212 | "name": "Clementina DuBuque", 213 | "username": "Moriah.Stanton", 214 | "email": "Rey.Padberg@karina.biz", 215 | "address": { 216 | "street": "Kattie Turnpike", 217 | "suite": "Suite 198", 218 | "city": "Lebsackbury", 219 | "zipcode": "31428-2261", 220 | "geo": { 221 | "lat": "-38.2386", 222 | "lng": "57.2232" 223 | } 224 | }, 225 | "phone": "024-648-3804", 226 | "website": "ambrose.net", 227 | "company": { 228 | "name": "Hoeger LLC", 229 | "catchPhrase": "Centralized empowering task-force", 230 | "bs": "target end-to-end models" 231 | } 232 | } 233 | ]; 234 | 235 | const PER_PAGE = 3; 236 | 237 | export default { 238 | '/api/users': (req, res) => { 239 | const { page } = req.query; 240 | setTimeout(() => { 241 | res.json({ 242 | totalUsers: users.length, 243 | totalPages: Math.ceil(users.length / PER_PAGE), 244 | users: users.slice(PER_PAGE * (page - 1), PER_PAGE * page), 245 | }); 246 | }, 500); 247 | }, 248 | } 249 | 250 | -------------------------------------------------------------------------------- /swr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "swr": "^0.1.6", 4 | "graphql-request": "^1.8.2" 5 | }, 6 | "devDependencies": { 7 | "umi-plugin-autolayout": "link:../.plugins/umi-plugin-autolayout" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /swr/pages/global-state.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #797DF2; 4 | } 5 | -------------------------------------------------------------------------------- /swr/pages/global-state.js: -------------------------------------------------------------------------------- 1 | import styles from './global-state.css'; 2 | import useGlobalState from "../hooks/useGlobalState"; 3 | 4 | export default function() { 5 | const { data, setData } = useGlobalState(); 6 | 7 | return ( 8 |
    9 |

    Global State

    10 |

    name: { data ? data.name : '' }

    11 | 16 |
    17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /swr/pages/graphql.js: -------------------------------------------------------------------------------- 1 | import { request } from 'graphql-request'; 2 | import useSWR from "swr"; 3 | 4 | const API = 'https://api.graph.cool/simple/v1/movies'; 5 | 6 | export default function() { 7 | const { data, error } = useSWR( 8 | `{ 9 | Movie(title: "Inception") { 10 | releaseDate 11 | actors { 12 | name 13 | } 14 | } 15 | }`, 16 | query => request(API, query), 17 | ); 18 | 19 | return ( 20 |
    21 |

    Graphql

    22 | { 23 | data ? (
    24 | Movie Release Date: {data.Movie.releaseDate} 25 |
    ) : 'loading...' 26 | } 27 |
    28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /swr/pages/index.js: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr'; 2 | import fetch from '../fetch'; 3 | 4 | export default function() { 5 | const { data: readers, error: readersError } = useSWR('/api/readers', fetch, { revalidateOnFocus: false }); 6 | const { data: status, error: statusError } = useSWR('/api/status', fetch, { revalidateOnFocus: false }); 7 | const { data: count, error: countError } = useSWR( 8 | () => `/api/count?count=${readers.length}`, 9 | fetch, 10 | { revalidateOnFocus: false }, 11 | ); 12 | 13 | function renderReaders() { 14 | if (readersError) return
    failed to load: {readersError.message}
    ; 15 | if (!readers) return
    loading...
    ; 16 | return ( 17 |
      18 | { 19 | readers.map(({ id, name }) => ( 20 |
    • {name}
    • 21 | )) 22 | } 23 |
    24 | ); 25 | } 26 | 27 | function renderStatus() { 28 | if (statusError) return
    failed to load: {statusError.message}
    ; 29 | if (!status) return
    loading...
    ; 30 | return ( 31 |
    { status.messageCount }
    32 | ); 33 | } 34 | 35 | function renderCount() { 36 | if (countError) return
    failed to load: {countError.message}
    ; 37 | if (!count) return
    loading...
    ; 38 | return ( 39 |
    { count.count }
    40 | ); 41 | } 42 | 43 | return ( 44 |
    45 |

    Readers

    46 | { renderReaders() } 47 |

    Status

    48 | { renderStatus() } 49 |

    Count

    50 | { renderCount() } 51 |
    52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /swr/pages/tab-storage-sync.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #E479F2; 4 | } 5 | -------------------------------------------------------------------------------- /swr/pages/tab-storage-sync.js: -------------------------------------------------------------------------------- 1 | import useSWR, { mutate } from 'swr'; 2 | import storage from "../storage"; 3 | import styles from './tab-storage-sync.css'; 4 | 5 | export default function() { 6 | const { data = { name: '' } } = useSWR('user-name', storage); 7 | 8 | function handleChange(ev) { 9 | localStorage.setItem( 10 | 'user-name', 11 | JSON.stringify({ name: ev.target.value }), 12 | ); 13 | mutate('user-name', { name: ev.target.value }); 14 | } 15 | 16 | return ( 17 |
    18 |

    Page tab-storage-sync

    19 | 20 | 21 |
    22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /swr/pages/todos.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import fetch from "../fetch"; 3 | import useTodos from "../hooks/useTodos"; 4 | 5 | export default function() { 6 | const { data, revalidate } = useTodos(); 7 | const [value, setValue] = useState(''); 8 | 9 | return data ? ( 10 |
    11 |

    Todos

    12 |
      13 | { 14 | data.map(todo => { 15 | return
    • { todo }
    • ; 16 | }) 17 | } 18 |
    19 |
    { 20 | e.preventDefault(); 21 | setValue(''); 22 | await fetch(`/api/todos/add?text=${value}`); 23 | revalidate(); 24 | }}> 25 | setValue(ev.target.value)} /> 26 |
    27 | 31 |
    32 | ) : 'loading...'; 33 | } 34 | -------------------------------------------------------------------------------- /swr/pages/users.js: -------------------------------------------------------------------------------- 1 | import { router } from "umi"; 2 | import useUsers from "../hooks/useUsers"; 3 | 4 | export default function() { 5 | const { data, page, setPage } = useUsers('1'); 6 | 7 | function changePageHandler(currPage) { 8 | setPage(currPage); 9 | } 10 | 11 | function renderPages() { 12 | const ret = []; 13 | let index = 1; 14 | while (index <= data.totalPages) { 15 | ret.push({index}); 20 | index += 1; 21 | } 22 | return ret; 23 | } 24 | 25 | return ( 26 |
    27 |

    Users

    28 | { 29 | data ? (<> 30 |
      31 | { 32 | data.users.map(u => { 33 | return
    • [{u.id}] { u.name }
    • ; 34 | }) 35 | } 36 |
    37 |
    38 | { 39 | renderPages() 40 | } 41 |
    42 | ) : 'loading...' 43 | } 44 |
    45 | 48 |
    49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /swr/storage.js: -------------------------------------------------------------------------------- 1 | 2 | export default async function (key) { 3 | const value = localStorage.getItem(key); 4 | if (!value) return undefined; 5 | return JSON.parse(value); 6 | } 7 | -------------------------------------------------------------------------------- /umi-hooks/.umirc.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | plugins: [ 4 | 'umi-plugin-autolayout' 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /umi-hooks/README.md: -------------------------------------------------------------------------------- 1 | # @umijs/hooks 2 | 3 | ## Links 4 | 5 | * https://github.com/umijs/hooks 6 | 7 | -------------------------------------------------------------------------------- /umi-hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@umijs/hooks": "^1.3.1" 4 | }, 5 | "devDependencies": { 6 | "umi-plugin-autolayout": "link:../.plugins/umi-plugin-autolayout" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /umi-hooks/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #95F279; 4 | } 5 | -------------------------------------------------------------------------------- /umi-hooks/pages/index.js: -------------------------------------------------------------------------------- 1 | 2 | import styles from './index.css'; 3 | 4 | export default function() { 5 | return ( 6 |
    7 |

    Page index

    8 |
    9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /umi-hooks/pages/use-async.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useAsync } from '@umijs/hooks'; 3 | 4 | export default function() { 5 | const [ id, setId ] = useState(0); 6 | const { data, loading, run, cancel, error } = useAsync( 7 | () => fetch(`https://randomuser.me/api?id=${id}`).then(r => r.json()), 8 | [id], 9 | ); 10 | return ( 11 |
    12 |

    Page use-async

    13 |
    14 | 15 | 16 | 17 |
    18 |
    19 | { loading ? 'loading...' : null } 20 | { (!loading && data) ?

    {data.results[0].email}

    : '' } 21 |
    22 |
    23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /unstated-next/.umirc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: ['umi-plugin-autolayout'], 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /unstated-next/README.md: -------------------------------------------------------------------------------- 1 | # unstated-next 2 | 3 | ## Links 4 | 5 | * https://github.com/jamiebuilds/unstated-next 6 | -------------------------------------------------------------------------------- /unstated-next/containers/Count.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { createContainer } from 'unstated-next'; 3 | 4 | function useCount(initialState = 0) { 5 | const [count, setState] = useState(initialState); 6 | 7 | function increment() { 8 | setState(count + 1); 9 | } 10 | 11 | function decrement() { 12 | setState(count - 1); 13 | } 14 | 15 | return { 16 | count, 17 | increment, 18 | decrement, 19 | }; 20 | } 21 | 22 | export default createContainer(useCount); 23 | -------------------------------------------------------------------------------- /unstated-next/layouts/index.js: -------------------------------------------------------------------------------- 1 | import Count from "../containers/Count"; 2 | 3 | export default function (props) { 4 | return ( 5 | 6 | { props.children } 7 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /unstated-next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "unstated-next": "^1.1.0" 4 | }, 5 | "devDependencies": { 6 | "umi-plugin-autolayout": "link:../.plugins/umi-plugin-autolayout" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /unstated-next/pages/components/CountDisplay.js: -------------------------------------------------------------------------------- 1 | import Count from '../../containers/Count'; 2 | 3 | export default function() { 4 | let { count, decrement, increment } = Count.useContainer(); 5 | return ( 6 |
    7 | 8 | {count} 9 | 10 |
    11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /unstated-next/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #79F2D2; 4 | } 5 | -------------------------------------------------------------------------------- /unstated-next/pages/index.js: -------------------------------------------------------------------------------- 1 | import styles from './index.css'; 2 | import Count from '../containers/Count'; 3 | import CountDisplay from './components/CountDisplay'; 4 | 5 | export default function() { 6 | return ( 7 |
    8 |

    Page index

    9 | 10 | 11 |
    12 |
    13 | 14 |
    15 |
    16 |
    17 |
    18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /unstated-next/pages/users.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #E079F2; 4 | } 5 | -------------------------------------------------------------------------------- /unstated-next/pages/users.js: -------------------------------------------------------------------------------- 1 | 2 | import styles from './users.css'; 3 | 4 | export default function() { 5 | return ( 6 |
    7 |

    Page users

    8 |
    9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /zustand/.umirc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: ['umi-plugin-autolayout'], 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /zustand/README.md: -------------------------------------------------------------------------------- 1 | # zustand 2 | 3 | ## Links 4 | 5 | * https://github.com/react-spring/zustand 6 | 7 | -------------------------------------------------------------------------------- /zustand/hooks/store.js: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | 3 | const [createStore] = create(set => ({ 4 | count: 0, 5 | increment: () => set(state => ({ count: state.count + 1 })), 6 | decrement: () => set(state => ({ count: state.count - 1 })), 7 | })); 8 | 9 | export default createStore; 10 | -------------------------------------------------------------------------------- /zustand/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "zustand": "^2.2.1" 4 | }, 5 | "devDependencies": { 6 | "umi-plugin-autolayout": "link:../.plugins/umi-plugin-autolayout" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /zustand/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #F279CA; 4 | } 5 | -------------------------------------------------------------------------------- /zustand/pages/index.js: -------------------------------------------------------------------------------- 1 | import styles from './index.css'; 2 | import createStore from "../hooks/store"; 3 | 4 | export default function() { 5 | const { 6 | count, increment, decrement, 7 | } = createStore(); 8 | 9 | return ( 10 |
    11 |

    Page index

    12 |

    count: { count }

    13 | 14 | 15 |
    16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /zustand/pages/users.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #F2D879; 4 | } 5 | -------------------------------------------------------------------------------- /zustand/pages/users.js: -------------------------------------------------------------------------------- 1 | 2 | import styles from './users.css'; 3 | 4 | export default function() { 5 | return ( 6 |
    7 |

    Page users

    8 |
    9 | ); 10 | } 11 | --------------------------------------------------------------------------------