)
140 | }
141 |
142 | const makeFetch = async (opt?: Options) => {
143 | try {
144 | update({ loading: true, called: true })
145 | fetcher.get(fetcherName).called = true
146 | const data: T = await this.fetch(url, opt || {})
147 |
148 | update({ loading: false, data, called: true })
149 | return data
150 | } catch (error) {
151 | update({ loading: false, error, called: true })
152 | throw error
153 | }
154 | }
155 |
156 | const refetch: Refetch = async (opt?: Options): Promise
=> {
157 | update({ loading: true, called: true })
158 | const refetchedData: any = await makeFetch(opt)
159 | return refetchedData as P
160 | }
161 |
162 | const argsRef = useRef({
163 | resolve: isResolve(options.params) && isResolve(options.query) && isResolve(options.body),
164 | params: getArg(options.params),
165 | query: getArg(options.query),
166 | body: getArg(options.body),
167 | })
168 |
169 | if (
170 | !argsRef.current.resolve &&
171 | getArg(options.params) &&
172 | getArg(options.query) &&
173 | getArg(options.body)
174 | ) {
175 | argsRef.current = {
176 | resolve: true,
177 | params: getArg(options.params),
178 | query: getArg(options.query),
179 | body: getArg(options.body),
180 | }
181 | }
182 |
183 | const getOpt = (options: Options): Options => {
184 | if (Object.keys(argsRef.current.params).length) {
185 | options.params = argsRef.current.params
186 | }
187 | if (Object.keys(argsRef.current.query).length) {
188 | options.query = argsRef.current.query
189 | }
190 | if (Object.keys(argsRef.current.body).length) {
191 | options.body = argsRef.current.body
192 | }
193 |
194 | return options
195 | }
196 |
197 | // TODO: 要确保 args resolve
198 | const start: Start = (opt): any => {
199 | if (opt?.params) argsRef.current.params = opt?.params
200 | if (opt?.query) argsRef.current.query = opt?.query
201 | if (opt?.body) argsRef.current.body = opt?.body
202 | setShouldStart(true)
203 | }
204 |
205 | useEffect(() => {
206 | // store refetch fn to fetcher
207 | if (!fetcher.get(fetcherName)) {
208 | fetcher.set(fetcherName, { refetch, called: false } as FetcherItem)
209 | }
210 |
211 | // if resolve, 说明已经拿到最终的 args
212 | const shouldFetch =
213 | argsRef.current.resolve && !fetcher.get(fetcherName).called && !isUnmouted() && shouldStart
214 |
215 | if (shouldFetch) {
216 | const opt = getOpt(options)
217 | makeFetch(opt)
218 | }
219 | }, [argsRef.current, shouldStart])
220 |
221 | // when unmount
222 | useUnmount(() => {
223 | // 全部 unmount,设置 called false
224 | const store = Storage.get(fetcherName)
225 |
226 | // 对应的 hooks 全部都 unmount 了
227 | if (store && store.setters.length === 0) {
228 | // 重新设置为 false,以便后续调用刷新
229 | fetcher.get(fetcherName).called = false
230 |
231 | // TODO: 要为true ? 还是 undefined 好
232 | update({ loading: true, called: false } as any)
233 | }
234 | })
235 |
236 | return { ...result, refetch, start } as HooksResult
237 | }
238 |
239 | useUpdate = (url: string, options: RequestOptions = {}) => {
240 | options.method = options.method || 'POST'
241 | const fetcherName = getFetcherName(url, options)
242 | const initialState = { loading: false, called: false } as UpdateResult
243 | const [result, setState] = useStore(fetcherName, initialState)
244 |
245 | const updateData = async (updateOptions: RequestOptions = {}) => {
246 | try {
247 | setState({ loading: true } as UpdateResult)
248 | const data: T = await this.fetch(url, { ...options, ...updateOptions })
249 | const nextState = { loading: false, called: true, data } as UpdateResult
250 | setState(nextState)
251 | return nextState
252 | } catch (error) {
253 | const nextState = { loading: false, called: true, error } as UpdateResult
254 | setState(nextState)
255 | return nextState
256 | }
257 | }
258 |
259 | const update = async (updateOptions: RequestOptions = {}): Promise => {
260 | return await updateData(updateOptions)
261 | }
262 |
263 | const out: [Update, UpdateResult] = [update, result]
264 |
265 | return out
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/packages/stook-rest/src/fetcher.ts:
--------------------------------------------------------------------------------
1 | import { Fetcher, FetcherItem } from './types'
2 |
3 | const NULL: any = null
4 | export class fetcher {
5 | private static store: Fetcher = {}
6 |
7 | static set(name: string, value: FetcherItem) {
8 | fetcher.store[name] = value
9 | }
10 |
11 | static get(name: string) {
12 | if (!fetcher.store[name]) {
13 | // fetcher.store[name] = {
14 | // refetch() {
15 | // const error = new Error(`[stook-rest]: In fetcher, can not get ${name}`)
16 | // console.warn(error)
17 | // },
18 | // } as FetcherItem
19 | fetcher.store[name] = NULL
20 | }
21 | return fetcher.store[name]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/stook-rest/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Client } from './Client'
2 |
3 | let baseURL: string = ''
4 |
5 | if (typeof window !== 'undefined') {
6 | baseURL = window.location.protocol + '//' + window.location.host
7 | }
8 |
9 | const client = new Client({ baseURL })
10 | const { fetch, useFetch, useUpdate, config, applyMiddleware } = client
11 |
12 | export * from './types'
13 | export * from './fetcher'
14 | export { Client, config, fetch, useFetch, useUpdate, applyMiddleware }
15 |
--------------------------------------------------------------------------------
/packages/stook-rest/src/types.ts:
--------------------------------------------------------------------------------
1 | import { Options as RequestOptions } from '@boter/request'
2 | import type { Context } from './Client'
3 |
4 | export type Update = (updateOptions?: RequestOptions) => Promise>
5 |
6 | export interface Options extends RequestOptions {
7 | key?: string
8 | initialData?: T
9 | lazy?: boolean
10 | onUpdate?(result: Result): any
11 | }
12 |
13 | export type Refetch = (options?: Options) => Promise
14 |
15 | export type Start = (options?: Options) => Promise
16 |
17 | export interface FetcherItem {
18 | refetch: Refetch
19 | start: Start
20 | result: Result
21 | called: boolean // is request called
22 | }
23 |
24 | export interface Fetcher {
25 | [key: string]: FetcherItem
26 | }
27 |
28 | export interface Result {
29 | loading: boolean
30 | called: boolean
31 | data: T
32 | error: any
33 | }
34 |
35 | export interface FetchResult extends Result {}
36 | export interface UpdateResult extends Result {}
37 |
38 | export interface RestOptions {
39 | baseURL: string
40 | headers?: HeadersInit
41 | middlewares?: Middleware[]
42 | }
43 |
44 | export interface HooksResult extends FetchResult {
45 | start: Start
46 | refetch: Refetch
47 | }
48 |
49 | export type Middleware = (ctx: Context, next: () => Promise) => any
50 |
--------------------------------------------------------------------------------
/packages/stook-rest/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { useRef, useCallback, EffectCallback, useEffect } from 'react'
2 |
3 | export function isFalsy(value: any) {
4 | if (typeof value === 'boolean') {
5 | return !value
6 | }
7 |
8 | return value === undefined || value === null
9 | }
10 |
11 | export function isResolve(arg: any) {
12 | if (!arg) return true
13 | if (typeof arg !== 'function') return true
14 | try {
15 | arg()
16 | return true
17 | } catch (error) {
18 | return false
19 | }
20 | }
21 |
22 | export const getArg = (arg: any) => {
23 | if (!arg) return {}
24 | if (typeof arg !== 'function') return arg
25 | try {
26 | return arg()
27 | } catch (error) {
28 | return false
29 | }
30 | }
31 |
32 | export function useUnmounted(): () => boolean {
33 | const unmountedRef = useRef(false)
34 | const isUnmouted = useCallback(() => unmountedRef.current, [])
35 |
36 | useEffect(() => {
37 | unmountedRef.current = false
38 | return () => {
39 | unmountedRef.current = true
40 | }
41 | })
42 |
43 | return isUnmouted
44 | }
45 |
46 | export const useEffectOnce = (effect: EffectCallback) => {
47 | useEffect(effect, [])
48 | }
49 |
50 | export const useUnmount = (fn: () => any): void => {
51 | const fnRef = useRef(fn)
52 |
53 | // update the ref each render so if it change the newest callback will be invoked
54 | fnRef.current = fn
55 |
56 | useEffectOnce(() => () => fnRef.current())
57 | }
58 |
--------------------------------------------------------------------------------
/packages/stook-rest/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "src",
4 | "types"
5 | ],
6 | "compilerOptions": {
7 | "module": "esnext",
8 | "lib": [
9 | "dom",
10 | "esnext"
11 | ],
12 | "importHelpers": true,
13 | "declaration": true,
14 | "sourceMap": true,
15 | "rootDir": "./src",
16 | "strict": true,
17 | "experimentalDecorators": true,
18 | "emitDecoratorMetadata": true,
19 | "noImplicitThis": true,
20 | "noImplicitAny": true,
21 | "strictNullChecks": true,
22 | "noUnusedLocals": true,
23 | "noUnusedParameters": true,
24 | "noImplicitReturns": true,
25 | "noFallthroughCasesInSwitch": true,
26 | "moduleResolution": "node",
27 | "baseUrl": "./",
28 | "paths": {
29 | "*": [
30 | "src/*",
31 | "node_modules/*"
32 | ]
33 | },
34 | "jsx": "react",
35 | "esModuleInterop": true
36 | }
37 | }
--------------------------------------------------------------------------------
/packages/stook-toggle/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # stook-toggle
2 |
3 | ## 1.17.0
4 |
5 | ### Minor Changes
6 |
7 | - add stook-async-storage
8 |
9 | ## 1.16.0
10 |
11 | ### Minor Changes
12 |
13 | - b2521ac: update stook-graphql
14 | - update stook-graphql
15 |
--------------------------------------------------------------------------------
/packages/stook-toggle/README.md:
--------------------------------------------------------------------------------
1 | # stook-toggle
2 |
3 | ## Installation
4 |
5 | ```bash
6 | npm i stook-toggle
7 | ```
8 |
9 | ## License
10 |
11 | [MIT License](https://github.com/forsigner/stook-toggle/blob/master/LICENSE)
12 |
--------------------------------------------------------------------------------
/packages/stook-toggle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stook-toggle",
3 | "version": "1.17.0",
4 | "license": "MIT",
5 | "author": "forsigner",
6 | "main": "dist/index.js",
7 | "module": "dist/stook-toggle.esm.js",
8 | "typings": "dist/index.d.ts",
9 | "files": [
10 | "dist"
11 | ],
12 | "scripts": {
13 | "start": "tsdx watch",
14 | "build": "tsdx build",
15 | "test": "tsdx test",
16 | "lint": "tsdx lint"
17 | },
18 | "devDependencies": {
19 | "stook": "^1.17.0"
20 | }
21 | }
--------------------------------------------------------------------------------
/packages/stook-toggle/src/index.ts:
--------------------------------------------------------------------------------
1 | import { useStore } from 'stook';
2 | import { useRef, useState } from 'react';
3 |
4 | const FALSE_AS_ANY: any = false;
5 |
6 | export function useToggle(
7 | key: string,
8 | initialState: S | [S, S] = FALSE_AS_ANY
9 | ): [S, () => any] {
10 | let tuple: any[] = [];
11 |
12 | if (typeof initialState === undefined) {
13 | tuple = [false, true];
14 | } else if (Array.isArray(initialState)) {
15 | tuple = initialState;
16 | } else {
17 | tuple = [initialState, !initialState];
18 | }
19 |
20 | const tupleContainer = useRef<[S, S]>(tuple as any);
21 | const [state, setState] = useStore(key, tuple[0]);
22 | const [index, setIndex] = useState(1);
23 |
24 | function toggle() {
25 | setState(tupleContainer.current[index]);
26 | setIndex(index => (index === 0 ? 1 : 0));
27 | }
28 |
29 | return [state, toggle];
30 | }
31 |
--------------------------------------------------------------------------------
/packages/stook-toggle/test/blah.test.ts:
--------------------------------------------------------------------------------
1 | describe('blah', () => {
2 | it('works', () => {
3 | expect(1 + 1).toEqual(2);
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/packages/stook-toggle/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "src",
4 | "types"
5 | ],
6 | "compilerOptions": {
7 | "module": "esnext",
8 | "lib": [
9 | "dom",
10 | "esnext"
11 | ],
12 | "importHelpers": true,
13 | "declaration": true,
14 | "sourceMap": true,
15 | "rootDir": "./src",
16 | "strict": true,
17 | "experimentalDecorators": true,
18 | "emitDecoratorMetadata": true,
19 | "noImplicitThis": true,
20 | "noImplicitAny": true,
21 | "strictNullChecks": true,
22 | "noUnusedLocals": true,
23 | "noUnusedParameters": true,
24 | "noImplicitReturns": true,
25 | "noFallthroughCasesInSwitch": true,
26 | "moduleResolution": "node",
27 | "baseUrl": "./",
28 | "paths": {
29 | "*": [
30 | "src/*",
31 | "node_modules/*"
32 | ]
33 | },
34 | "jsx": "react",
35 | "esModuleInterop": true
36 | }
37 | }
--------------------------------------------------------------------------------
/packages/stook/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # stook
2 |
3 | ## 1.17.0
4 |
5 | ### Minor Changes
6 |
7 | - add stook-async-storage
8 |
9 | ## 1.16.0
10 |
11 | ### Minor Changes
12 |
13 | - b2521ac: update stook-graphql
14 | - update stook-graphql
15 |
--------------------------------------------------------------------------------
/packages/stook/README.md:
--------------------------------------------------------------------------------
1 | # Stook
2 |
3 | [](https://www.npmjs.com/package/stook) [](https://coveralls.io/github/forsigner/stook?branch=master) [](https://bundlephobia.com/result?p=stook) [](https://github.com/prettier/prettier)
4 |
5 | A minimalist design state management library for React.
6 |
7 | ## Documentation
8 |
9 | The documentation site of stook is hosted at [https://stook.vercel.app](https://stook.vercel.app).
10 |
11 | ## Quick start
12 |
13 | **simplest**
14 |
15 | ```tsx
16 | import React from 'react'
17 | import { useStore } from 'stook'
18 |
19 | function Counter() {
20 | const [count, setCount] = useStore('Counter', 0)
21 | return (
22 |
23 |
You clicked {count} times
24 |
25 |
26 | )
27 | }
28 | ```
29 |
30 | **share state**
31 |
32 | ```jsx
33 | import React from 'react'
34 | import { useStore } from 'stook'
35 |
36 | function Counter() {
37 | const [count, setCount] = useStore('Counter', 0)
38 | return (
39 |
40 |
You clicked {count} times
41 |
42 |
43 | )
44 | }
45 |
46 | function Display() {
47 | const [count] = useStore('Counter')
48 | return {count}
49 | }
50 |
51 | function App() {
52 | return (
53 |
54 |
55 |
56 |
57 | )
58 | }
59 | ```
60 |
61 | ## License
62 |
63 | [MIT License](https://github.com/forsigner/stook/blob/master/LICENSE)
64 |
--------------------------------------------------------------------------------
/packages/stook/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stook",
3 | "version": "1.17.0",
4 | "description": "A minimalist design state management library for React",
5 | "author": "forsigner",
6 | "main": "dist/index.js",
7 | "module": "dist/stook.esm.js",
8 | "typings": "dist/index.d.ts",
9 | "files": [
10 | "dist"
11 | ],
12 | "scripts": {
13 | "start": "tsdx watch",
14 | "build": "tsdx build",
15 | "test": "tsdx test --env=jsdom",
16 | "test:watch": "tsdx test --watch",
17 | "test:cov": "tsdx test --coverage",
18 | "lint": "tsdx lint"
19 | },
20 | "bugs": {
21 | "url": "https://github.com/forsigner/stook/issues"
22 | },
23 | "homepage": "https://github.com/forsigner/stook",
24 | "license": "MIT",
25 | "dependencies": {
26 | "immer": "^9.0.12",
27 | "mitt": "^3.0.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/stook/src/Storage.ts:
--------------------------------------------------------------------------------
1 | import { Store } from './Store'
2 |
3 | interface Stores {
4 | [key: string]: Store
5 | }
6 |
7 | /**
8 | * Storage for anything
9 | */
10 | export class Storage {
11 | static stores: Stores = {}
12 | static set(key: any, value: Store) {
13 | const store = Storage.stores[key]
14 | if (!store || !store.setState) {
15 | Storage.stores[key] = value
16 | }
17 | }
18 |
19 | static get(key: any): Store {
20 | return Storage.stores[key]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/stook/src/Store.ts:
--------------------------------------------------------------------------------
1 | import { produce } from 'immer'
2 | import { Dispatch, SetStateAction } from './types'
3 | import { emitStoreUpdate } from './emitter'
4 |
5 | /**
6 | * store for one key
7 | */
8 | export class Store {
9 | state: S
10 | setters: Dispatch>[] = []
11 | constructor(value: any) {
12 | this.state = value
13 | }
14 |
15 | private getNextState(state: S, value: any): any {
16 | let nextState: any
17 |
18 | // not function
19 | if (typeof value !== 'function') return value
20 |
21 | // can not use immer
22 | if (typeof state !== 'object') return value(state)
23 |
24 | let useImmer = true
25 |
26 | const immerState = produce(state, draft => {
27 | const fnValue = value(draft)
28 | if (fnValue === draft) return // do nothing
29 |
30 | // use function return value
31 | if (fnValue && typeof fnValue === 'object') {
32 | nextState = fnValue
33 | useImmer = false
34 | }
35 | })
36 |
37 | if (useImmer) {
38 | nextState = immerState
39 | }
40 |
41 | return nextState
42 | }
43 |
44 | setState = (key: any, state: any, value: any): any => {
45 | const nextState = this.getNextState(state, value)
46 |
47 | this.state = nextState
48 |
49 | emitStoreUpdate({ key, nextState })
50 |
51 | this.state = nextState
52 | this.setters.forEach(set => set(nextState))
53 | return nextState
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/stook/src/emitter.ts:
--------------------------------------------------------------------------------
1 | import mitt from 'mitt'
2 |
3 | interface Data {
4 | key: any
5 | nextState: any
6 | }
7 |
8 | type Events = {
9 | STORE_INITED: string
10 | STORE_UPDATED: Data
11 | }
12 |
13 | enum EventKey {
14 | STORE_INITED = 'STORE_INITED',
15 | STORE_UPDATED = 'STORE_UPDATED',
16 | }
17 |
18 | export const emitter = mitt()
19 |
20 | export function emitStoreInit(key: any) {
21 | emitter.emit(EventKey.STORE_INITED, key)
22 | }
23 |
24 | export function emitStoreUpdate(data: Data) {
25 | emitter.emit(EventKey.STORE_UPDATED, data)
26 | }
27 |
28 | export function onStoreInit(cb: (data: string) => void) {
29 | emitter.on(EventKey.STORE_INITED, key => {
30 | cb(key)
31 | })
32 | }
33 |
34 | export function onStoreUpdate(cb: (data: Data) => void) {
35 | emitter.on(EventKey.STORE_UPDATED, data => {
36 | cb(data)
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/packages/stook/src/getState.ts:
--------------------------------------------------------------------------------
1 | import { Storage } from './Storage'
2 | import { keyType } from './types'
3 |
4 | const undefined_as_any: any = undefined
5 |
6 | /**
7 | * Get store by Key
8 | * @param key
9 | */
10 | export function getState(key: K | keyType): S {
11 | const store = Storage.get(key)
12 | return store ? store.state : undefined_as_any
13 | }
14 |
--------------------------------------------------------------------------------
/packages/stook/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Storage'
2 | export * from './useStore'
3 | export * from './getState'
4 | export * from './mutate'
5 | export * from './types'
6 | export { onStoreInit, onStoreUpdate } from './emitter'
7 |
--------------------------------------------------------------------------------
/packages/stook/src/mutate.ts:
--------------------------------------------------------------------------------
1 | import { Storage } from './Storage'
2 | import { keyType } from './types'
3 |
4 | /**
5 | * update store by key
6 | *
7 | * @param key unique store key (唯一key)
8 | * @param nextValue next value
9 | */
10 | export function mutate(key: K | keyType, nextValue?: S) {
11 | const store = Storage.get(key)
12 |
13 | if (store && store.setState) {
14 | return store.setState(key, store.state, nextValue)
15 | } else {
16 | // init state, if no store exist
17 | Storage.set(key, { state: nextValue } as any)
18 | return nextValue
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/stook/src/types.ts:
--------------------------------------------------------------------------------
1 | export type Action = S | ((prevState: S) => S) | ((prevState: S) => void)
2 | export type SetStateAction = S | ((prevState: S) => S)
3 | export type Dispatch = (value: A) => void
4 |
5 | export interface Key {
6 | }
7 |
8 | export type keyType = keyof Key
9 |
--------------------------------------------------------------------------------
/packages/stook/src/useStore.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useLayoutEffect } from 'react'
2 | import { Storage } from './Storage'
3 | import { Store } from './Store'
4 | import { Dispatch, Action, keyType } from './types'
5 | import { emitStoreInit } from './emitter'
6 |
7 | const isBrowser =
8 | typeof window === 'object' && typeof document === 'object' && document.nodeType === 9
9 |
10 | const useSafeLayoutEffect = isBrowser ? useLayoutEffect : useEffect
11 |
12 | /**
13 | * Returns a stateful value, similar to useState, but need a key;
14 | *
15 | * 用法和 useState 几乎一模一样,只是第一个参数是唯一key;
16 | *
17 | * @param key unique store key (唯一key)
18 | * @param initialValue initial value, can not override, use first useStore to init
19 | * @see https://stook.vercel.app/docs/stook/use-store
20 | *
21 | * 需要注意的是,如果调用多个相同key的 useStore, 第一个被调用的 useStore 的 initialValue 才是有效的 initialValue
22 | */
23 | export function useStore(
24 | key: K | keyType,
25 | initialValue?: S,
26 | ): [S, Dispatch>] {
27 | const storageStore = Storage.get(key)
28 | const initialState = storageStore ? storageStore.state : initialValue
29 |
30 | Storage.set(key, new Store(initialState))
31 |
32 | const newStore = Storage.get(key)
33 | const [state, set] = useState(initialState)
34 | const { setters } = newStore
35 |
36 | /**
37 | * push setState sync
38 | */
39 | useSafeLayoutEffect(() => {
40 | setters.push(set)
41 | emitStoreInit(key)
42 | }, [])
43 |
44 | useEffect(() => {
45 | return () => {
46 | setters.splice(setters.indexOf(set), 1)
47 | }
48 | }, [])
49 |
50 | function act(key: any): Dispatch> {
51 | return (value: any) => {
52 | return newStore.setState(key, state, value)
53 | }
54 | }
55 |
56 | return [state, act(key)]
57 | }
58 |
--------------------------------------------------------------------------------
/packages/stook/test/getState.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks'
2 | import { useStore, getState } from '../src'
3 |
4 | describe('mutate', () => {
5 | it('none state', () => {
6 | const count = getState('Counter1')
7 | expect(count).toBeUndefined()
8 | })
9 |
10 | it('can get state', () => {
11 | const {} = renderHook(() => useStore('Counter2', 2))
12 | const count = getState('Counter2')
13 | expect(count).toBe(2)
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/packages/stook/test/mutate.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderHook, act } from '@testing-library/react-hooks'
2 | import { useStore, mutate } from '../src'
3 |
4 | describe('mutate', () => {
5 | it('inited', () => {
6 | const { result } = renderHook(() => useStore('Counter1', 0))
7 |
8 | expect(result.current[0]).toBe(0)
9 |
10 | act(() => {
11 | mutate('Counter1', 10)
12 | })
13 |
14 | expect(result.current[0]).toBe(10)
15 | })
16 |
17 | it('did not inited', () => {
18 | act(() => {
19 | mutate('Counter2', 10)
20 | })
21 |
22 | const { result } = renderHook(() => useStore('Counter2', 0))
23 | expect(result.current[0]).toBe(10)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/packages/stook/test/useStore.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderHook, act } from '@testing-library/react-hooks'
2 | import { useStore } from '../src'
3 |
4 | describe('useStore', () => {
5 | it('simplest', () => {
6 | const { result } = renderHook(() => useStore('Counter', 0))
7 |
8 | expect(result.current[0]).toBe(0)
9 |
10 | act(() => {
11 | result.current[1](1)
12 | })
13 | expect(result.current[0]).toBe(1)
14 |
15 | act(() => {
16 | result.current[1](() => 2)
17 | })
18 | expect(result.current[0]).toBe(2)
19 | })
20 |
21 | it('setState(value)', () => {
22 | const { result } = renderHook(() => useStore('User', { name: 'fo' }))
23 |
24 | expect(result.current[0].name).toBe('fo')
25 |
26 | act(() => {
27 | result.current[1]({ name: 'foo' })
28 | })
29 | expect(result.current[0].name).toBe('foo')
30 |
31 | // function
32 | act(() => {
33 | result.current[1]((state: any) => ({ ...state, name: 'fooo' }))
34 | })
35 | expect(result.current[0].name).toBe('fooo')
36 |
37 | // immer
38 | act(() => {
39 | result.current[1]((state: any) => {
40 | state.name = 'foooo'
41 | })
42 | })
43 | expect(result.current[0].name).toBe('foooo')
44 | })
45 |
46 | it('try init again', () => {
47 | const { result: result1 } = renderHook(() => useStore('User2', { name: 'fo' }))
48 | const { result: result2 } = renderHook(() => useStore('User2', { name: 'bar' }))
49 |
50 | expect(result1.current[0].name).toBe('fo')
51 | expect(result2.current[0].name).toBe('fo')
52 | })
53 | })
54 |
--------------------------------------------------------------------------------
/packages/stook/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "src",
4 | "types"
5 | ],
6 | "compilerOptions": {
7 | "module": "esnext",
8 | "lib": [
9 | "dom",
10 | "esnext"
11 | ],
12 | "importHelpers": true,
13 | "declaration": true,
14 | "sourceMap": true,
15 | "rootDir": "./src",
16 | "strict": true,
17 | "experimentalDecorators": true,
18 | "emitDecoratorMetadata": true,
19 | "noImplicitThis": true,
20 | "noImplicitAny": true,
21 | "strictNullChecks": true,
22 | "noUnusedLocals": true,
23 | "noUnusedParameters": true,
24 | "noImplicitReturns": true,
25 | "noFallthroughCasesInSwitch": true,
26 | "moduleResolution": "node",
27 | "baseUrl": "./",
28 | "paths": {
29 | "*": [
30 | "src/*",
31 | "node_modules/*"
32 | ]
33 | },
34 | "jsx": "react",
35 | "esModuleInterop": true
36 | }
37 | }
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /node_modules
3 |
4 | # Production
5 | /build
6 |
7 | # Generated files
8 | .docusaurus
9 | .cache-loader
10 |
11 | # Misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | # Website
2 |
3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
4 |
5 | ## Installation
6 |
7 | ```console
8 | yarn install
9 | ```
10 |
11 | ## Local Development
12 |
13 | ```console
14 | yarn start
15 | ```
16 |
17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
18 |
19 | ## Build
20 |
21 | ```console
22 | yarn build
23 | ```
24 |
25 | This command generates static content into the `build` directory and can be served using any static contents hosting service.
26 |
27 | ## Deployment
28 |
29 | ```console
30 | GIT_USER= USE_SSH=true yarn deploy
31 | ```
32 |
33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
34 |
--------------------------------------------------------------------------------
/website/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/website/blog/2019-05-28-hola.md:
--------------------------------------------------------------------------------
1 | ---
2 | slug: hola
3 | title: Hola
4 | author: Gao Wei
5 | author_title: Docusaurus Core Team
6 | author_url: https://github.com/wgao19
7 | author_image_url: https://avatars1.githubusercontent.com/u/2055384?v=4
8 | tags: [hola, docusaurus]
9 | ---
10 |
11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
12 |
--------------------------------------------------------------------------------
/website/blog/2019-05-29-hello-world.md:
--------------------------------------------------------------------------------
1 | ---
2 | slug: hello-world
3 | title: Hello
4 | author: Endilie Yacop Sucipto
5 | author_title: Maintainer of Docusaurus
6 | author_url: https://github.com/endiliey
7 | author_image_url: https://avatars1.githubusercontent.com/u/17883920?s=460&v=4
8 | tags: [hello, docusaurus]
9 | ---
10 |
11 | Welcome to this blog. This blog is created with [**Docusaurus 2 alpha**](https://docusaurus.io/).
12 |
13 |
14 |
15 | This is a test post.
16 |
17 | A whole bunch of other information.
18 |
--------------------------------------------------------------------------------
/website/blog/2019-05-30-welcome.md:
--------------------------------------------------------------------------------
1 | ---
2 | slug: welcome
3 | title: Welcome
4 | author: Yangshun Tay
5 | author_title: Front End Engineer @ Facebook
6 | author_url: https://github.com/yangshun
7 | author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4
8 | tags: [facebook, hello, docusaurus]
9 | ---
10 |
11 | Blog features are powered by the blog plugin. Simply add files to the `blog` directory. It supports tags as well!
12 |
13 | Delete the whole directory if you don't want the blog features. As simple as that!
14 |
--------------------------------------------------------------------------------
/website/docs/devtools/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: 简介
4 | sidebar_label: 简介
5 | ---
6 |
7 | 为了更好的编程体验,Stook 支持使用 Redux DevTools 调试。
8 |
9 | ## Install Redux DevTools extension
10 |
11 | 如果你未安装 Redux DevTools extension,请安装相应浏览器的插件:[Redux DevTools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd)。
12 |
13 | ## Setup
14 |
15 | 使用 devtools 非常简单,先安装 `stook-devtools` 包:
16 |
17 | ```bash
18 | npm i stook-devtools
19 | ```
20 |
21 | 然后在项目代码中进入,并初始化:
22 |
23 | ```js
24 | import { devtools } from 'devtools'
25 |
26 | devtools.init()
27 | ```
28 |
29 | 如果你不想在生产环境引入:
30 |
31 | ```js
32 | import { devtools } from 'devtools'
33 |
34 | if (process.env.NODE_ENV !== 'production') {
35 | devtools.init()
36 | }
37 | ```
38 |
39 | ## 效果
40 |
41 | 生效后,可以在浏览器的 Redux DevTools extension 看到整个应用的 state 状态:
42 |
43 | 
44 |
--------------------------------------------------------------------------------
/website/docs/graphql/config.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: config
3 | title: Config
4 | sidebar_label: Config
5 | ---
6 |
7 | ## 全局配置
8 |
9 | 你可以使用 `config` 方法进行全局配置,全局配置将在每个请求生效:
10 |
11 | ```tsx
12 | import { config } from 'stook-graphql'
13 |
14 | config({
15 | endpoint: 'https://graphql-compose.herokuapp.com/user',
16 | headers: {
17 | foo: 'bar',
18 | },
19 | })
20 | ```
21 |
22 | ## 配置选项
23 |
24 | **`endpoint`**: string
25 |
26 | GraphQL Client 服务器端点, 默认为 /graphql。
27 |
28 | **`headers`**: object
29 |
30 | 每个请求都会带上的请求头,默认为 `{ 'content-type': 'application/json; charset=utf-8' }`
31 |
32 | ## Creating an instance
33 |
34 | 在某些应用场景,你可以能有多个后端服务,这时你需要多个 Client 实例:
35 |
36 | ```js
37 | const client = new Client({
38 | endpoint: 'https://graphql-compose.herokuapp.com/user',
39 | headers: {
40 | foo: 'bar',
41 | },
42 | })
43 | ```
44 |
45 | 创建实例后,你同样可以调用这些 Api:
46 |
47 | - query
48 | - useQuery
49 | - useMutation
50 | - useSubscription
51 |
--------------------------------------------------------------------------------
/website/docs/graphql/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: Introduction
4 | sidebar_label: Introduction
5 | ---
6 |
7 | 有人说,GraphQL 是未来,这里不讨论 GraphQL 和 RESTful 谁更优秀。`stook-graphql`和 `stook-rest`一样,推崇使用 hooks 获取并维护异步数据。
8 |
9 | ## 使用 `stook-graphql`
10 |
11 | 我们使用 `stook-rest` 的 `useFetch` 获取数据,可以轻松的拿到数据的状态 `{ loading, data, error }`,然后渲染处理:
12 |
13 | ```jsx
14 | import React from 'react'
15 | import { useQuery } from 'stook-graphql'
16 |
17 | const GET_USER = `
18 | query User {
19 | userOne {
20 | _id
21 | name
22 | gender
23 | age
24 | }
25 | }
26 | `
27 | const User = () => {
28 | const { loading, data, error } = useQuery(GET_USER)
29 |
30 | if (loading) return loading....
31 | if (error) return error!
32 |
33 | return {JSON.stringify(data, null, 2)}
34 | }
35 | ```
36 |
37 | 这是最简单的 GraphQL 用法,下面章节你会学习到如何配置 endpoint、refetch、mutate 等更多详细的用法。
38 |
--------------------------------------------------------------------------------
/website/docs/graphql/middleware.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: middleware
3 | title: Middleware
4 | sidebar_label: Middleware
5 | ---
6 |
7 | ## 中间件用法
8 |
9 | 为了方便的方便的拦截请求,stook-graphql 提供了中间件机制。
10 |
11 | ```js
12 | import { applyMiddleware } from 'stook-graphql'
13 |
14 | // add a middleware
15 | applyMiddleware(async (ctx, next) => {
16 | ctx.headers.Authorization = `bearer token...`
17 | await next()
18 | ctx.body = { data: ctx.body }
19 | })
20 |
21 | // add another middleware
22 | applyMiddleware(async (ctx, next) => {
23 | try {
24 | await next()
25 | } catch (error) {
26 | ctx.body = { error: ctx.body }
27 | }
28 | })
29 | ```
30 |
31 | ## 中间件机制
32 |
33 | stook-graphql 的机制和 [koa](https://github.com/koajs/koa) 类似,都是洋葱模型。
34 |
35 | 每个中间件是一个 async 函数,类型定义如下:
36 |
37 | ```ts
38 | type Middleware = (ctx: Ctx, next: () => Promise) => anyjs
39 |
40 | type NextFn = () => Promise
41 |
42 | interface Ctx {
43 | headers: {
44 | [key: string]: string
45 | }
46 | body: any
47 | }
48 | ```
49 |
50 | ## 使用场景
51 |
52 | 下面是一些实际应用场景的例子:
53 |
54 | **为所有请求添加 token**
55 |
56 | ```js
57 | applyMiddleware(async (ctx, next) => {
58 | ctx.headers.Authorization = `bearer my_token_qazxsw`
59 | await next()
60 | })
61 | ```
62 |
63 | **格式化 response**
64 |
65 | ```js
66 | applyMiddleware(async (ctx, next) => {
67 | await next()
68 | ctx.body = {
69 | code: 0,
70 | data: ctx.body,
71 | msg: 'fetch data success',
72 | }
73 | })
74 | ```
75 |
76 | **统一错误处理**
77 |
78 | ```js
79 | applyMiddleware(async (ctx, next) => {
80 | try {
81 | await next()
82 | } catch (error) {
83 | throw {
84 | code: 1,
85 | error: error,
86 | msg: 'fetch data error',
87 | }
88 | }
89 | })
90 | ```
91 |
--------------------------------------------------------------------------------
/website/docs/graphql/query.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: query
3 | title: query
4 | sidebar_label: query
5 | ---
6 |
7 | ```jsx
8 | import React, { useState, useEffect } from 'react'
9 | import { query } from 'stook-graphql'
10 |
11 | const GET_USER = `
12 | query User {
13 | userById(_id: "57bb44dd21d2befb7ca3f010") {
14 | name
15 | gender
16 | age
17 | }
18 | }
19 | `
20 | export default () => {
21 | const [data, setData] = useState()
22 |
23 | useEffect(() => {
24 | async function queryData() {
25 | const res = await query(GET_USER)
26 | setData(res)
27 | }
28 | queryData()
29 | }, [])
30 |
31 | return {JSON.stringify(data, null, 2)}
32 | }
33 |
34 | ```
--------------------------------------------------------------------------------
/website/docs/graphql/quick-start.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: quick-start
3 | title: Quick start
4 | sidebar_label: Quick start
5 | ---
6 |
7 | stook-graphql 一个基于 hooks 实现的 Graphql 数据请求工具。
8 |
9 | ## 安装
10 |
11 | ```bash
12 | npm i stook-graphql
13 | ```
14 |
15 | ## 获取数据
16 |
17 | 下面展示如何快速获取 GraphQL Api 数据。你就可以使用 `stook-graphql` 提供的一个 hooks `useQuery`来获取远程服务器数据。
18 |
19 | ```tsx
20 | import { useQuery } from 'stook-graphql'
21 |
22 | interface User {
23 | userOne: {
24 | _id: string
25 | name: string
26 | gender: string
27 | age: number
28 | }
29 | }
30 |
31 | const User = () => {
32 | const { loading, data, error } = useQuery(GET_USER)
33 |
34 | if (loading) return loading....
35 | if (error) return error!
36 | return {JSON.stringify(data, null, 2)}
37 | }
38 | ```
39 |
40 | 当然,这只是 `useQuery` 最基本功能,如果你想深入了解它的其他功能,比如 refetch、retry 等高级功能,你看详情阅读 `useQuery` Api。
41 |
42 | ## 下一步
43 |
44 | 上面就是用获取数据最简单的例子,如果你要深入了解如何使用 `stook-graphql`,建议细看:
45 |
46 | - [获取数据](/docs/graphql/useQuery): 深入了解 `useFetch` 的使用
47 | - [更新数据](/docs/graphql/useMutation): 深入了解 `useMutation` 的使用
48 | - [网络请求](/docs/graphql/query): 深入了解 `query` 的使用
49 |
--------------------------------------------------------------------------------
/website/docs/graphql/useMutate.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: useMutation
3 | title: useMutation
4 | sidebar_label: useMutation
5 | ---
6 |
7 | ```jsx
8 | import React from 'react'
9 | import { useMutation } from 'stook-graphql'
10 |
11 | const GET_USER = `
12 | query User {
13 | userById(_id: "57bb44dd21d2befb7ca3f010") {
14 | name
15 | gender
16 | age
17 | }
18 | }
19 | `
20 |
21 | export default () => {
22 | const [addTodo, { loading, data, error }] = useMutation(GET_USER)
23 | return (
24 |
25 |
29 |
30 | {error &&
{JSON.stringify(error, null, 2)}
}
31 | {data &&
{JSON.stringify(data, null, 2)}
}
32 |
33 | )
34 | }
35 | ```
36 |
--------------------------------------------------------------------------------
/website/docs/graphql/useQuery.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: useQuery
3 | title: useQuery
4 | sidebar_label: useQuery
5 | ---
6 |
7 | > const result = useQuery(input, options)
8 |
9 | 以简单高效的方式获取和管理异步数据是 stook-query 的核心功能。接下来,你将学习如何通过 `useQuery` 获取数据并渲染成 UI。
10 |
11 | 下面是一个示例,这里假设你已经配置好 client,如果不了解如何配置,请看 [配置](/docs/graphql/config)。
12 |
13 | ## 使用 `useQuery`
14 |
15 | ```jsx
16 | import React from 'react'
17 | import { useQuery } from 'stook-graphql'
18 |
19 | const GET_USER = `
20 | query User {
21 | userById(_id: "57bb44dd21d2befb7ca3f010") {
22 | name
23 | gender
24 | age
25 | }
26 | }
27 | `
28 |
29 | export default () => {
30 | const { loading, data, error } = useQuery(GET_USER)
31 |
32 | if (loading) return loading....
33 | if (error) return {JSON.stringify(error, null, 2)}
34 |
35 | return {JSON.stringify(data, null, 2)}
36 | }
37 | ```
38 |
39 | ## input (string)
40 |
41 | GraphQL 请求的字符串。
42 |
43 | ## options
44 |
45 | **`variables: boolean`**
46 |
47 | GraphQL 变量。
48 |
49 | **`key?: string`**
50 |
51 | 该请求的唯一标识符,因为 stook-graphql 是基于 stook,这个 key 就是 stook 的唯一 key,对于 refetch 非常有用。默认是为 input。
52 |
53 | **`headers?: HeadersInit;`**
54 |
55 | HTTP 请求头,和原生`fetch`的 [`Headers`](https://github.github.io/fetch/#Headers) 一致,但有默认值: `{ 'content-type': 'application/json; charset=utf-8' }`
56 |
57 | **pollInterval?: number**
58 |
59 | 轮询时间间隔
60 |
61 | **lazy?: boolean**
62 |
63 | 默认不发请求,需要手动 start 触发,start 是 result 里面的方法。
64 |
65 | **errRetryCount?: number**
66 |
67 | 错误重试次数
68 |
69 | **timeout?: number**
70 |
71 | 超时时间(单位毫秒)
72 |
73 | ## 结果 (Result)
74 |
75 | **`loading: boolean`**
76 |
77 | 一个布尔值,表示数据是否加载中。
78 |
79 | **`data: T`**
80 |
81 | 服务器返回的数据。
82 |
83 | **`error: GraphqlError`**
84 |
85 | 服务器返回错误。
86 |
87 | **`refetch: (options?: Options) => Promise`**
88 |
89 | 重新发起一个请求获取数据。
90 |
91 | **`start: (options?: Options) => Promise`**
92 |
93 | 手动触发一个请求获取数据,配合 lazy 使用。
94 |
--------------------------------------------------------------------------------
/website/docs/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: intro
4 | sidebar_label: intro
5 | ---
6 |
7 | ..
8 | .
9 |
--------------------------------------------------------------------------------
/website/docs/rest/config.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: config
3 | title: Config
4 | sidebar_label: Config
5 | ---
6 |
7 | ## 全局配置
8 |
9 | 你可以使用 `config` 方法进行全局配置,全局配置将在每个请求生效:
10 |
11 | ```tsx
12 | import { config } from 'stook-rest'
13 |
14 | config({
15 | baseURL: 'https://jsonplaceholder.typicode.com',
16 | headers: {
17 | foo: 'bar',
18 | },
19 | })
20 | ```
21 |
22 | ## 配置选项
23 |
24 | **`baseURL`**: string
25 |
26 | Restful Api 服务器 baseURL, 默认为当前前端页面 host。
27 |
28 | **`headers`**: object
29 |
30 | 每个请求都会带上的请求头,默认为 `{ 'content-type': 'application/json; charset=utf-8' }`
31 |
32 | ## Creating an instance
33 |
34 | 在某些应用场景,你可以能有多个后端服务,这时你需要多个 Client 实例:
35 |
36 | ```js
37 | const client = new Client({
38 | baseURL: 'https://jsonplaceholder.typicode.com',
39 | headers: {
40 | foo: 'bar',
41 | },
42 | })
43 |
44 | client.fetch('/todos').then((data) => {
45 | console.log(data)
46 | })
47 | ```
48 |
49 | 创建实例后,你同样可以调用这些 Api:
50 |
51 | - fetch
52 | - useFetch
53 | - useUpdate
54 |
--------------------------------------------------------------------------------
/website/docs/rest/custom-hooks.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: custom-hooks
3 | title: Custom hooks
4 | sidebar_label: Custom hooks
5 | ---
6 |
7 | 在真实的业务开发中,不建议直接在组件中使用 `useFetch`,更推荐是使用使用自定义 hooks 对请求的业务逻辑进行封装。
8 |
9 | ## 如何自定义 hooks ?
10 |
11 | ```jsx
12 | const useFetchTodos = () => {
13 | const { loading, data: todos = [], error } = useFetch('/todos')
14 | return { loading, todos, error }
15 | }
16 | ```
17 |
18 | ## 为何推荐自定义 hooks ?
19 |
20 | 自定义 hooks 有下面几点好处:
21 |
22 | ### 为 hooks 命名
23 |
24 | 这看上去和直接使用 `useFetch` 没有太大区别,实际上它增加了代码的可读性。
25 |
26 | ### 文件更易管理
27 |
28 | 如果我们我们直接在组件中使用 `useFetch`,我们需要在组件引入非常多文件。这个请求数据只有一个组件使用还好,如果多个组件需要共享此请求数据,文件管理将会非常乱。
29 |
30 | ```tsx
31 | import React from 'react'
32 | import { useFetch } from 'stook-rest'
33 | import { Todo } from '../../typings'
34 | import { GET_TODO } from '../../URL.constant'
35 |
36 | export default () => {
37 | const { data: todos } = useFetch(GET_TODO)
38 |
39 | if (loading) return loading....
40 | return (
41 |
42 |
Todo:
43 |
{JSON.stringify(todo, null, 2)}
44 |
45 | )
46 | }
47 | ```
48 |
49 | 如果使用使用自定义 hooks,我们只需在组件中引入 hooks:
50 |
51 | ```tsx
52 | import React from 'react'
53 | import { useFetchTodos } from '../../useFetchTodos'
54 |
55 | export default () => {
56 | const { loading, todos } = useFetchTodos()
57 |
58 | if (loading) return loading....
59 | return (
60 |
61 |
Todos:
62 |
{JSON.stringify(todos, null, 2)}
63 |
64 | )
65 | }
66 | ```
67 |
68 | ### 更好管理 computed value
69 |
70 | 为了业务逻辑更好的复用,我们经常会使用 computed value:
71 |
72 | ```tsx
73 | const useFetchTodos = () => {
74 | const { loading, data: todos = [], error } = useFetch('/todos')
75 | const count = todos.length
76 | const completedCount = todos.filter(i => i.completed).length
77 | return { loading, todos, count, completedCount, error }
78 | }
79 | ```
80 |
81 | ### 更优雅地共享数据
82 |
83 | 自定义 hooks 让数据跨组件共享数据更加优雅:
84 |
85 | ```tsx
86 | interface Todo {
87 | id: number
88 | title: string
89 | completed: boolean
90 | }
91 |
92 | const useFetchTodos = () => {
93 | const { loading, data: todos = [], error } = useFetch('/todos')
94 | const count = todos.length
95 | const completedCount = todos.filter(i => i.completed).length
96 | return { loading, todos, count, completedCount, error }
97 | }
98 |
99 | const TodoList = () => {
100 | const { loading, todos, count, completedCount } = useFetchTodos()
101 | if (loading) return loading....
102 | return (
103 |
104 |
TodoList:
105 |
todos count: {count}
106 |
completed count: {completedCount}
107 |
{JSON.stringify(todos, null, 2)}
108 |
109 | )
110 | }
111 |
112 | const ReuseTodoList = () => {
113 | const { loading, todos, count, completedCount } = useFetchTodos()
114 | if (loading) return loading....
115 | return (
116 |
117 |
ReuseTodoList:
118 |
todos count: {count}
119 |
completed count: {completedCount}
120 |
{JSON.stringify(todos, null, 2)}
121 |
122 | )
123 | }
124 |
125 | export default () => (
126 |
127 |
128 |
129 |
130 | )
131 | ```
132 |
--------------------------------------------------------------------------------
/website/docs/rest/dependent.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: dependent
3 | title: Dependent Fetching
4 | sidebar_label: Dependent Fetching
5 | ---
6 |
7 | 很多时候,一个请求会依赖另外一个请求的数据,这时候请求会有前后顺序,stook-rest 可以非常优雅的处理这种依赖请求:
8 |
9 | ```jsx
10 | import React from 'react'
11 | import { config, useFetch } from 'stook-rest'
12 |
13 | export default () => {
14 | const { data: todos } = useFetch('/todos')
15 |
16 | const { loading, data: todo } = useFetch('/todos/:id', {
17 | params: () => ({ id: todos[9].id }),
18 | })
19 |
20 | if (loading) return loading....
21 |
22 | return (
23 |
24 |
Todo:
25 |
{JSON.stringify(todo, null, 2)}
26 |
27 | )
28 | }
29 | ```
30 |
31 | 我们知道,`params`、`query`、`body` 三中参数值通常是一个对象,其实他们也可以是一个函数,函数参数可以让我们轻易地使用依赖请求。
32 |
33 | 依赖请求的方式可以大大地减少你的代码量,并让你可以用类似同步的代码书写数据请求代码。
34 |
35 | [](https://codesandbox.io/s/sweet-lake-gu2el?fontsize=14&hidenavigation=1&theme=dark)
36 |
--------------------------------------------------------------------------------
/website/docs/rest/fetch.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: fetch
3 | title: fetch
4 | sidebar_label: fetch
5 | ---
6 |
7 | 大部分情况下,建议使用 `useFetch` 获取数据。不过有些场景,你不需要在维护异步数据的状态,你只需要发送普通的网络请求。
8 |
9 | 比如,点击一个按钮提交表单,执行一个更新操作:
10 |
11 | **todoService.ts**
12 |
13 | ```jsx
14 | import { createStore } from 'stook-store'
15 | import { fetch } from 'stook-rest'
16 |
17 | export async function fetchTodos() {
18 | return await fetch('/todos')
19 | }
20 | ```
21 |
22 | stook-rest 的 `fetch` 和原生的 Api Fetch (https://github.github.io/fetch/)非常类似,但又有一些区别。
23 |
24 | 那 stook-rest 的 `fetch` 和原生 `fetch`有什么区别呢?
25 |
26 | ## 直接返回数据
27 |
28 | ```js
29 | const todos = await fetch('/todos')
30 | ```
31 |
32 | 上面的 todos 就是服务器的数据,你可以指定如何处理远程数据:
33 |
34 | ```js
35 | const todos = await fetch('/todos', { type: 'text' })
36 | ```
37 |
38 | type 是可选的,默认为 'json',类型为 `type Type = 'text' | 'json' | 'blob' | 'arrayBuffer' | 'formData'`
39 |
40 | ## Request body 支持 JS 对象
41 |
42 | ```js
43 | const todos = await fetch('/todos', {
44 | method: 'POST',
45 | body: {
46 | title: 'do something',
47 | },
48 | })
49 | ```
50 |
51 | ## 支持 query 参数
52 |
53 | ```js
54 | const todos = await fetch('/todos', {
55 | query: {
56 | pageSize: 10,
57 | pageNum: 1,
58 | },
59 | })
60 | ```
61 |
62 | 将请求 `/todos?pageSize=10&pageNum=1`,甚至你可以使用嵌套的 query 对象。
63 |
64 | ## 支持 params 参数
65 |
66 | ```js
67 | const todos = await fetch('/todos/:id', {
68 | params: { id: 1 },
69 | })
70 | ```
71 |
72 | 处理这些不一样,其他参数跟原生 `fetch`保持一致,详情请看文档:https://github.github.io/fetch 。
73 |
--------------------------------------------------------------------------------
/website/docs/rest/interceptor.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: interceptor
3 | title: 拦截器
4 | sidebar_label: 拦截器
5 | ---
6 |
7 | wip...
--------------------------------------------------------------------------------
/website/docs/rest/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: Introduction
4 | sidebar_label: Introduction
5 | ---
6 |
7 | 异步数据管理一直是一个难点,在 React 的生态圈中,很多人把异步数据使用状态管理维护,比如使用 Redux,用异步 Action 获取远程数据。我个人不喜欢使用 Redux 状态管理维护异步数据,我更倾向于在组件内直接获取异步数据,使用 hooks,简化数据的获取和管理。
8 |
9 | 为什么要使用 Hooks 管理呢?可以看 [React 异步数据管理思考](https://zhuanlan.zhihu.com/p/64648552)
10 |
11 | 下面我们看看和使用 Redux 有什么本质上的区别。
12 |
13 | 假设我们要实现一个功能,获取一个 TodoList 数据,并且用 UI 渲染。
14 |
15 | TodoList 数据源:https://jsonplaceholder.typicode.com/todos 。
16 |
17 | ## 使用 `stook-rest`
18 |
19 | 我们使用 `stook-rest` 的 `useFetch` 获取数据,可以轻松的拿到数据的状态 `{ loading, data, error }`,然后渲染处理:
20 |
21 | ```jsx
22 | import React from 'react'
23 | import { useFetch } from 'stook-rest'
24 |
25 | const Todos = () => {
26 | const { loading, data, error } = useFetch('https://jsonplaceholder.typicode.com/todos')
27 |
28 | if (loading) return loading...
29 | if (error) return error!
30 |
31 | return (
32 |
33 | {data.map(item => (
34 | - {item.title}
35 | ))}
36 |
37 | )
38 | }
39 |
40 | export default Todos
41 | ```
42 |
43 | [](https://codesandbox.io/s/bitter-frog-t2tbm?fontsize=14&hidenavigation=1&theme=dark)
44 |
45 | 如果你是 graphql 用户,类似的,可以使用 `stook-graphql` 的 `useQuery`。
46 |
47 | ## 使用 Redux
48 |
49 | 使用 Redux 管理异步数据,假设我们已经使用了 Redux 中间件 `redux-thunk`,我们会有下面类似的代码:
50 |
51 | 首先,我们会把字符串定义定义为常量到一个 `constant.js`
52 |
53 | ```js
54 | export const LOADING_TODOS = 'LOADING_TODOS'
55 | export const LOAD_TODOS_SUCCESS = 'LOAD_TODOS_SUCCESS'
56 | export const LOAD_TODOS_ERROR = 'LOAD_TODOS_ERROR'
57 | ```
58 |
59 | 然后,编写异步的 action, `actions.js`:
60 |
61 | ```js
62 | import { LOADING_TODOS, LOAD_TODOS_SUCCESS, LOAD_TODOS_ERROR } from '../constant'
63 |
64 | export function fetchTodos() {
65 | return dispatch => {
66 | dispatch({ type: LOADING_TODOS })
67 | return fetch('https://jsonplaceholder.typicode.com/todo')
68 | .then(response => response.json())
69 | .then(todos => {
70 | dispatch({
71 | type: LOAD_TODOS_SUCCESS,
72 | todos,
73 | })
74 | })
75 | .catch(error => {
76 | dispatch({
77 | type: LOAD_TODOS_ERROR,
78 | error,
79 | })
80 | })
81 | }
82 | }
83 | ```
84 |
85 | 接着,在 reducer 中处理数据,`reducer.js`
86 |
87 | ```js
88 | import { LOADING_TODOS, LOAD_TODOS_SUCCESS, LOAD_TODOS_ERROR } from '../constant'
89 |
90 | const initialState = {
91 | todos: {
92 | loading: false,
93 | data: [],
94 | error: null,
95 | },
96 | }
97 |
98 | export default function(state = initalState, action) {
99 | switch (action.type) {
100 | case LOADING_TODOS:
101 | const { todos } = state
102 | return { ...state, todos: { ...todos, loading: true } }
103 | case LOAD_TODOS_SUCCESS:
104 | const { todos } = state
105 | return { ...state, todos: { ...todos, data: action.todos } }
106 | case LOAD_TODOS_ERROR:
107 | const { todos } = state
108 | return { ...state, todos: { ...todos, error: action.error } }
109 | default:
110 | return state
111 | }
112 | }
113 | ```
114 |
115 | 还没完,最后,在组件中使用:
116 |
117 | ```jsx
118 | import React, { Component } from 'react'
119 | import { connect } from 'react-redux'
120 | import { fetchTodos } from '../actions'
121 |
122 | class Todos extends Component {
123 | componentDidMount() {
124 | const { dispatch } = this.props
125 | dispatch(fetchTodos)
126 | }
127 |
128 | render() {
129 | const { loading, items, error } = this.props
130 | if (loading) return loading...
131 | if (error) return error!
132 |
133 | return (
134 |
135 | {items.map(item => (
136 | - {item.title}
137 | ))}
138 |
139 | )
140 | }
141 | }
142 |
143 | const mapStateToProps = state => {
144 | const { todos } = state
145 | return {
146 | loading: todos.loaing,
147 | items: todos.data,
148 | error: todos.error,
149 | }
150 | }
151 |
152 | export default connect(mapStateToProps)(Todos)
153 | ```
154 |
155 | [](https://codesandbox.io/s/xjl84rjvno?fontsize=14&hidenavigation=1&theme=dark)
156 |
157 | 我们可以发现,使用 Redux 管理异步数据,代码量激增,是 `stook-rest` 5 倍以上的代码量,不管开发效率还是开发体验,亦或是可以维护性和可读性,个人认为,类似的 redux 这样的解决方案并不优秀。 Hooks 简单直观,Redux 本地冗长,并且链路太长,需维护多个文件,更多的代码量。
158 |
--------------------------------------------------------------------------------
/website/docs/rest/middleware.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: middleware
3 | title: Middleware
4 | sidebar_label: Middleware
5 | ---
6 |
7 | ## 中间件用法
8 |
9 | 为了方便的方便的拦截请求,stook-graphql 提供了中间件机制。
10 |
11 | ```js
12 | import { applyMiddleware } from 'stook-graphql'
13 |
14 | // add a middleware
15 | applyMiddleware(async (ctx, next) => {
16 | ctx.headers.Authorization = `bearer token...`
17 | await next()
18 | ctx.body = { data: ctx.body }
19 | })
20 |
21 | // add another middleware
22 | applyMiddleware(async (ctx, next) => {
23 | try {
24 | await next()
25 | } catch (error) {
26 | ctx.body = { error: ctx.body }
27 | }
28 | })
29 | ```
30 |
31 | ## 中间件机制
32 |
33 | stook-graphql 的机制和 [koa](https://github.com/koajs/koa) 类似,都是洋葱模型。
34 |
35 | 每个中间件是一个 async 函数,类型定义如下:
36 |
37 | ```ts
38 | type Middleware = (ctx: Ctx, next: () => Promise) => anyjs
39 |
40 | type NextFn = () => Promise
41 |
42 | interface Ctx {
43 | headers: {
44 | [key: string]: string
45 | }
46 | body: any
47 | }
48 | ```
49 |
50 | ## 使用场景
51 |
52 | 下面是一些实际应用场景的例子:
53 |
54 | **为所有请求添加 token**
55 |
56 | ```js
57 | applyMiddleware(async (ctx, next) => {
58 | ctx.headers.Authorization = `bearer my_token_qazxsw`
59 | await next()
60 | })
61 | ```
62 |
63 | **格式化 response**
64 |
65 | ```js
66 | applyMiddleware(async (ctx, next) => {
67 | await next()
68 | ctx.body = {
69 | code: 0,
70 | data: ctx.body,
71 | msg: 'fetch data success',
72 | }
73 | })
74 | ```
75 |
76 | **统一错误处理**
77 |
78 | ```js
79 | applyMiddleware(async (ctx, next) => {
80 | try {
81 | await next()
82 | } catch (error) {
83 | throw {
84 | code: 1,
85 | error: error,
86 | msg: 'fetch data error',
87 | }
88 | }
89 | })
90 | ```
91 |
--------------------------------------------------------------------------------
/website/docs/rest/quick-start.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: quick-start
3 | title: Quick start
4 | sidebar_label: Quick start
5 | ---
6 |
7 | stook-rest 一个基于 hooks 实现的 Restful Api 数据请求工具。
8 |
9 | ## 安装
10 |
11 | ```bash
12 | npm i stook-rest
13 | ```
14 |
15 | ## 获取数据
16 |
17 | 下面展示如何快速获取 Restful Api 数据。你就可以使用 `stook-rest` 提供的一个 hooks `useFetch`来获取远程服务器数据。下面是获取 todos 列表并渲染到组件,可以看到,代码相当简洁:
18 |
19 | ```tsx
20 | import { useFetch } from 'stook-rest'
21 |
22 | interface Todo {
23 | id: number
24 | title: string
25 | completed: boolean
26 | }
27 |
28 | const Todos = () => {
29 | const { loading, data, error } = useFetch('/todos')
30 |
31 | if (loading) return loading...
32 | if (error) return error!
33 |
34 | return (
35 |
36 | {data.map(item => (
37 | - {item.title}
38 | ))}
39 |
40 | )
41 | }
42 | ```
43 |
44 | [](https://codesandbox.io/s/bitter-frog-t2tbm?fontsize=14&hidenavigation=1&theme=dark)
45 |
46 | 当然,这只是 `useFetch` 最基本功能,如果你想深入了解它的其他功能,比如 refetch、retry 等高级功能,你看详情阅读 `useFetch` Api。
47 |
48 | ## 下一步
49 |
50 | 上面就是用获取数据最简单的例子,如果你要深入了解如何使用 `stook-rest`,建议细看:
51 |
52 | - [获取数据](/docs/rest/useFetch): 深入了解 `useFetch` 的使用
53 | - [更新数据](/docs/rest/useUpdate): 深入了解 `useUpdate` 的使用
54 | - [网络请求](/docs/rest/fetch): 深入了解 `fetch` 的使用
55 |
--------------------------------------------------------------------------------
/website/docs/rest/refetch.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: refetch
3 | title: refetch
4 | sidebar_label: refetch
5 | ---
6 |
7 | 很多场景中,你需要更新异步数据,比如在 CRUD 功能中,新增、删除、修改、分页、筛选等功能都需要更新异步数据。`stook-rest` 提供了三中方式更新数据,三种方式可在不同业务场景中使用,这是`stook-rest`的重要功能之一,你应该仔细阅读并理解它的使用场景,使用这种方式管理异步数据,整个应用的状态将变得更加简单,代码量会成本的减少,相应的可维护性大大增加。
8 |
9 | ## 重新获取数据的三种方式
10 |
11 | 但很多时候,你需要更新异步数据,`stook-rest`提供三种方式更新数据:
12 |
13 | - 内部 Refetch
14 | - 更新依赖 deps
15 | - 使用 fetcher
16 |
17 | ## 内部 Refetch
18 |
19 | 这是最简单的重新获取数据的方式,通常,如果触发更新的动作和`useFetch`在统一组件内,可以使用这种方式。
20 |
21 | ```tsx
22 | const Todos = () => {
23 | const { loading, data, error, refetch } = useFetch('/todos', {
24 | query: { _start: 0, _limit: 5 }, // first page
25 | })
26 |
27 | if (loading) return loading...
28 | if (error) return error!
29 |
30 | const getSecondPage = () => {
31 | refetch({
32 | query: { _start: 5, _limit: 5 }, // second page
33 | })
34 | }
35 |
36 | return (
37 |
38 |
39 |
40 | {data.map(item => (
41 | - {item.title}
42 | ))}
43 |
44 |
45 | )
46 | }
47 | ```
48 |
49 | [](https://codesandbox.io/s/vigilant-bouman-y0gu7?fontsize=14&hidenavigation=1&theme=dark)
50 |
51 | ## 更新依赖 deps
52 |
53 | 通过更新依赖来重新获取数据,这也是常用的方式之一,因为在很多业务场景中,触发动作会在其他组件中,下面演示如何通过更新依赖触发数据更新:
54 |
55 | ```tsx
56 | import { useState } from 'react'
57 | import { useFetch } from 'stook-rest'
58 |
59 | export default () => {
60 | const [count, setCount] = useState(1)
61 | const { loading, data, error } = useFetch('/todos', {
62 | deps: [count],
63 | })
64 |
65 | if (loading) return loading...
66 | if (error) return error!
67 |
68 | const update = () => {
69 | setCount(count + 1)
70 | }
71 |
72 | return (
73 |
74 |
75 |
76 | {data.map(item => (
77 | - {item.title}
78 | ))}
79 |
80 |
81 | )
82 | }
83 | ```
84 | [](https://codesandbox.io/s/loving-cray-b6xvq?fontsize=14&hidenavigation=1&theme=dark)
85 |
86 | 你可以在任意地方,不管组件内还是组件外,你都可以更新依赖,从而实现数据更新。
87 |
88 | 注意:这里的依赖是个对象,你必须更新整个对象的引用,如果你只更新对象的属性是无效的。
89 |
90 | ## 使用 fetcher
91 |
92 | 有时候,你需要在组件外部重新获取数据,但`useFetch` 却没有任何可以被依赖的参数,这时你可以使用 fetcher:
93 |
94 | ```tsx
95 | import { useFetch, fetcher } from 'stook-rest'
96 |
97 | const Todos = () => {
98 | const { loading, data, error } = useFetch('/todos', { key: 'GetTodos' })
99 |
100 | if (loading) return loading...
101 | if (error) return error!
102 |
103 | return (
104 |
105 | {data.map(item => (
106 | - {item.title}
107 | ))}
108 |
109 | )
110 | }
111 |
112 | const Refresh = () =>
113 |
114 | const TodoApp = () => (
115 |
116 |
117 |
118 |
119 | )
120 | ```
121 |
122 | [](https://codesandbox.io/s/stoic-bardeen-y15mg?fontsize=14&hidenavigation=1&theme=dark)
123 |
124 | 使用 fetcher 是,你需要为`useFetch` 提供 name 参数,用法是:`fetcher['name'].refetch()`,这里的 `refetch` 和内部 `refetch` 是同一个函数,所以它也有 options 参数。
125 |
126 | ## 高级用法
127 |
128 | 使用 fetcher 时,为一个 HTTP 请求命名 (name) 不是必须的,每个 HTTP 请求都有一个默认的名字,默认名字为该请求的 url 参数。
129 |
130 | 为了项目代码的可维护性,推荐把所以 Api 的 url 集中化,比如:
131 |
132 | ```tsx
133 | // apiService.ts
134 | enum Api {
135 | GetTodo = 'GET /todos/:id',
136 | GetTodos = 'GET /todos',
137 | }
138 |
139 | export default Api
140 | ```
141 |
142 | 在组件中:
143 |
144 | ```tsx
145 | import { useFetch, fetcher } from 'stook-rest'
146 | import Api from '@service/apiService'
147 |
148 | const Todos = () => {
149 | const { loading, data, error } = useFetch(Api.GetTodos)
150 |
151 | if (loading) return loading...
152 | if (error) return error!
153 |
154 | return (
155 |
156 |
157 |
158 | {data.map(item => (
159 | - {item.title}
160 | ))}
161 |
162 |
163 | )
164 | }
165 | ```
166 |
--------------------------------------------------------------------------------
/website/docs/rest/share-state.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: share-state
3 | title: Share state
4 | sidebar_label: Share state
5 | ---
6 |
7 | ## 使用
8 |
9 | stook-rest 另一个强大的特性是请求数据的共享,由于 stook-rest 底层的数据管理是基于 stook 的,所以跨组件共享数据将变得非常简单:
10 |
11 | ```jsx
12 | const TodoItem = () => {
13 | const { loading, data: todo } = useFetch('/todos/1')
14 | if (loading) return loading....
15 | return (
16 |
17 |
{JSON.stringify(todo, null, 2)}
18 |
19 | )
20 | }
21 |
22 | const ReuseTodoItem = () => {
23 | const { loading, data: todo } = useFetch('/todos/1')
24 | if (loading) return loading....
25 | return (
26 |
27 |
ReuseTodoItem:
28 |
{JSON.stringify(todo, null, 2)}
29 |
30 | )
31 | }
32 |
33 | export default () => (
34 |
35 |
36 |
37 |
38 | )
39 | ```
40 |
41 | [](https://codesandbox.io/s/wizardly-ellis-nqmqj?fontsize=14&hidenavigation=1&theme=dark)
42 |
43 | 上面我们在两个组件中使用了 `useFetch`,它们的唯一 key 是一样的 (都是 `GET /todos/1`),而且只会发送一次请求,两个组件会使用同一份数据。
44 |
45 | ## 优化
46 |
47 | 个人不太建议直接在多个组件使用同一个 `useFetch`,更进一步使用自定义 hooks,增强业务逻辑的复用性:
48 |
49 | ```jsx
50 | const useFetchTodo = () => {
51 | const { loading, data: todo, error } = useFetch('/todos/1')
52 | return { loading, todo, error }
53 | }
54 |
55 | const TodoItem = () => {
56 | const { loading, todo } = useFetchTodo()
57 | if (loading) return loading....
58 | return (
59 |
60 |
TodoItem:
61 |
{JSON.stringify(todo, null, 2)}
62 |
63 | )
64 | }
65 |
66 | const ReuseTodoItem = () => {
67 | const { loading, todo } = useFetchTodo()
68 | if (loading) return loading....
69 | return (
70 |
71 |
ReuseTodoItem:
72 |
{JSON.stringify(todo, null, 2)}
73 |
74 | )
75 | }
76 |
77 | export default () => (
78 |
79 |
80 |
81 |
82 | )
83 | ```
84 |
85 | [](https://codesandbox.io/s/blue-glitter-zysrb?fontsize=14&hidenavigation=1&theme=dark)
86 |
--------------------------------------------------------------------------------
/website/docs/rest/useFetch.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: useFetch
3 | title: useFetch
4 | sidebar_label: useFetch
5 | ---
6 |
7 | > const result = useFetch(url, options)
8 |
9 | 以简单高效的方式获取和管理异步数据是 stook-rest 的核心功能。接下来,你将学习如何通过 `useFetch` 获取数据并渲染成 UI。
10 |
11 | 下面是一个示例,这里假设你已经配置好 client,如果不了解如何配置,请看 [配置](/docs/rest/config)。
12 |
13 | ## 使用 `useFetch`
14 |
15 | ```jsx
16 | import { useFetch } from 'stook-rest'
17 |
18 | interface Todo {
19 | id: number
20 | title: string
21 | completed: boolean
22 | }
23 |
24 | const Todos = () => {
25 | const { loading, data, error } = useFetch('/todos')
26 |
27 | if (loading) return loading...
28 | if (error) return error!
29 |
30 | return (
31 |
32 | {data.map(item => (
33 | - {item.title}
34 | ))}
35 |
36 | )
37 | }
38 | ```
39 |
40 | [](https://codesandbox.io/s/bitter-frog-t2tbm?fontsize=14&hidenavigation=1&theme=dark)
41 |
42 | ## URL(string)
43 |
44 | HTTP 请求的 URL,eg: "/todos"。
45 |
46 | ## options
47 |
48 | **`method?: Method`**
49 |
50 | HTTP 请求的类型,默认为 `GET`, 全部可选值: `type Method = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH' | 'HEAD'`
51 |
52 | ```js
53 | const { loading, data, error } = useFetch('/todos', { method: 'POST' })
54 | ```
55 |
56 | **`query?: Query`**
57 |
58 | HTTP 请求的 query 对象,通常 `GET` 类型的请求比较常用。
59 |
60 | ```js
61 | const { loading, data, error } = useFetch('/todos', {
62 | query: { pageNum: 1, pageSize: 20 }
63 | })
64 | ```
65 |
66 | 上面会把 url 转换为: `/todos?pageNum=1&pageSize=20`。详细的转换规则请参照 [qs](https://github.com/ljharb/qs)
67 |
68 | **`body?: Body`**
69 |
70 | HTTP 请求的 body 对象,和原生 `fetch` 的 [body](https://github.github.io/fetch/#request-body) 类似,不同的是,`useFetch` 的 body 支持 JS 对象:
71 |
72 | ```js
73 | const { loading, data, error } = useFetch('/todos', {
74 | body: { title: 'todo1' },
75 | })
76 | ```
77 |
78 | **`params?: Params`**
79 |
80 | URL 的参数对象,用法如下:
81 |
82 | ```js
83 | const { loading, data, error } = useFetch('/todos/:id', {
84 | params: { id: 10 },
85 | })
86 | ```
87 |
88 | 请求发送后, `/todos/:id` 会转换为 `/todos/10`。
89 |
90 | **`headers?: HeadersInit;`**
91 |
92 | HTTP 请求头,和原生`fetch`的 [`Headers`](https://github.github.io/fetch/#Headers) 一致,但有默认值: `{ 'content-type': 'application/json; charset=utf-8' }`
93 |
94 | **`deps?: Deps`**
95 |
96 | `useFetch` 是一个自定义的 React hooks,默认情况下,组件多次渲染,`useFetch` 只会执行一次,不过如果你设置了依赖 (deps),并且依赖发生更新,`useFetch`会重新执行,就是会重新获取数据,其机制类似于 `useEffect` 的依赖,不同的是不设置任何依赖值时,当组件发生多次渲染,`useFetch` 只会执行一次,`useFetch` 执行多次。
97 |
98 | 依赖值 deps 是个数组,类型为:`type Deps = ReadonlyArray`
99 |
100 | **`key?: string`**
101 |
102 | 该请求的唯一标识符,因为 stook-rest 是基于 stook,这个 key 就是 stook 的唯一 key,对于 refetch 非常有用。默认是为 `${method} ${url}`,比如请求如下:
103 |
104 | ```js
105 | const { loading, data } = useFetch('/todos', { method: 'POST' })
106 | ```
107 |
108 | 那默认的 key 为: `POST /todos`
109 |
110 | ## 结果 (Result)
111 |
112 | **`loading: boolean`**
113 |
114 | 一个布尔值,表示数据是否加载中。
115 |
116 | **`data: T`**
117 |
118 | 服务器返回的数据。
119 |
120 | **`error: RestError`**
121 |
122 | 服务器返回错误。
123 |
124 | **`refetch: (options?: Options) => Promise`**
125 |
126 | 重新发起一个请求获取数据,eg:
127 |
128 | ```tsx
129 | const Todos = () => {
130 | const { loading, data, error, refetch } = useFetch('/todos')
131 |
132 | if (loading) return loading...
133 | if (error) return error!
134 |
135 | return (
136 |
137 |
138 |
139 | {data.map(item => (
140 | - {item.title}
141 | ))}
142 |
143 |
144 | )
145 | }
146 | ```
147 |
--------------------------------------------------------------------------------
/website/docs/rest/useUpdate.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: useUpdate
3 | title: useUpdate
4 | sidebar_label: useUpdate
5 | ---
6 |
7 | `useFetch` 通常用于获取数据,`useUpdate` 通常用于创建/更新/删除数据。
8 |
9 | ## 基本用法
10 |
11 | 下面是一个添加一个 TodoItem 的例子,使用 `useUpdate` 你可以轻易的获取到网络请求的各种状态:
12 |
13 | ```jsx
14 | export default () => {
15 | const [addTodo, { loading, called, data, error }] = useUpdate('/todos')
16 |
17 | return (
18 |
19 |
29 |
30 | {error &&
{JSON.stringify(error, null, 2)}
}
31 | {data &&
{JSON.stringify(data, null, 2)}
}
32 |
33 | )
34 | }
35 | ```
36 |
37 | ## 更新反馈
38 |
39 | 通常更新数据成功后,我们需要提示用户,使用`useUpdate` 可以有两种方式实现交互反馈:
40 |
41 | **第一种,直接判断 data 和 error 的状态:**
42 |
43 | ```jsx
44 | export default () => {
45 | const [addTodo, { loading, called, data, error }] = useUpdate('/todos')
46 |
47 | if (data) {
48 | alert('Add Todo successfully')
49 | }
50 |
51 | if (error) {
52 | alert('Add Todo fali')
53 | }
54 |
55 | return (
56 |
57 |
67 |
68 | {error &&
{JSON.stringify(error, null, 2)}
}
69 | {data &&
{JSON.stringify(data, null, 2)}
}
70 |
71 | )
72 | }
73 | ```
74 |
75 | **第一种,在 update 函数中获取 data 和 error 的状态:**
76 |
77 | ```jsx
78 | export default () => {
79 | const [update, { loading, called, data, error }] = useUpdate('/todos')
80 |
81 | const addTodo = async () => {
82 | const { data } = await update({
83 | body: { title: 'new TODO' },
84 | })
85 | if (data) {
86 | alert('Add Todo successfully')
87 | }
88 | }
89 |
90 | return (
91 |
92 |
96 |
97 | {error &&
{JSON.stringify(error, null, 2)}
}
98 | {data &&
{JSON.stringify(data, null, 2)}
}
99 |
100 | )
101 | }
102 | ```
103 |
104 | ## Options
105 |
106 | `useUpdate` 的 options 和 `useFetch` 基本一样。
--------------------------------------------------------------------------------
/website/docs/stook/custom-hooks.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: custom-hooks
3 | title: Custom hooks
4 | sidebar_label: Custom hooks
5 | ---
6 |
7 | 为了能使组件和状态管理逻辑分离,强烈建议使用自定义 hooks 管理状态,比如说你要管理 Counter 的状态,那就是自定义一个叫 `useCounter` 的 hooks,然后在各组件中使用 `useCounter()`, 而不是直接使用 `useStore('Counter')`。
8 |
9 | 示例:
10 |
11 | ```jsx
12 | import React from 'react'
13 | import { useStore } from 'stook'
14 |
15 | function useCounter() {
16 | const [count, setCount] = useStore('Counter', 0)
17 | const decrease = () => setCount(count - 1)
18 | const increase = () => setCount(count + 1)
19 | return { count, increase, decrease }
20 | }
21 |
22 | function Display() {
23 | const { count } = useCounter()
24 | return {count}
25 | }
26 |
27 | function Increase() {
28 | const { increase } = useCounter()
29 | return +
30 | }
31 |
32 | function Decrease() {
33 | const { decrease } = useCounter()
34 | return -
35 | }
36 |
37 | export default function App() {
38 | return (
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 | ```
47 |
48 | [](https://codesandbox.io/s/nameless-shadow-ozke5?fontsize=14&hidenavigation=1&theme=dark)
49 |
50 | 上面三个子组件,都用到了 useCounter,它们实现了状态共享。
51 |
--------------------------------------------------------------------------------
/website/docs/stook/faq.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: faq
3 | title: FAQ
4 | sidebar_label: FAQ
5 | ---
6 |
7 | ## 和 Redux、Mobx 有什么区别? 可以代替他们吗?
8 |
9 | 和 Redux 相比,Stook 更简单,学习成本更低,对 TypeScript 支持更好,相同点是两者都是 immutable 的。
10 |
11 | 和 Mobx 相比, Mobx 是 mutable 的,Stook 是类 Redux 的 immutable。
12 |
13 | Stook 和 Redux、Mobx 是解决同一个问题的不同解决方案,他们之间可以互相替代,也可以混合使用,Stook 会更灵活,因为它能即插即用。
14 |
15 | ## 支持 TypeScript 吗?
16 |
17 | 完美支持,详情请看[TypeScript](docs/stook/typescript)。
18 |
19 | ## 性能怎样?会有 "Too many re-renders"问题吗?
20 |
21 | 性能跟原始的 useState 无差别,遵循 useState 的用法,就不会有 "Too many re-renders" 问题。
22 |
--------------------------------------------------------------------------------
/website/docs/stook/get-state.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: get-state
3 | title: getState
4 | sidebar_label: getState
5 | ---
6 |
7 | 在某些场景,你可能需要更灵活的读取 state,这时你可以使用 `getState`,比如下面两种场景:
8 |
9 | - 在组件外读取 state
10 | - 在组件内读取 state,却不订阅其更新
11 |
12 | ## 在组件外读取 state
13 |
14 | 为了业务逻辑能和组件渲染分离,我们把 `handleSubmit` 放在组件外:
15 |
16 | ```jsx
17 | import React from 'react'
18 | import { useStore, getState } from 'stook'
19 |
20 | function submitUser() {
21 | const name = getState('[UserForm]')
22 | alert(name)
23 | }
24 |
25 | export const UserForm = () => {
26 | const [name, setName] = useStore('[UserForm]', 'initial name')
27 | return (
28 |
29 | setName(e.target.value)} />
30 |
31 |
32 | )
33 | }
34 | ```
35 |
36 | [](https://codesandbox.io/s/cool-framework-4mziz?fontsize=14&hidenavigation=1&theme=dark)
37 |
38 | 对于大型项目,我们可能会为项目的架构分层,我们可能会有 service 层,这时 `getState` 就非常有用:
39 |
40 | `user.service.ts`
41 |
42 | ```jsx
43 | export function submitUser() {
44 | const name = getState('[UserForm]')
45 | alert(name)
46 | }
47 | ```
48 |
49 | `UserForm.ts`
50 |
51 | ```jsx
52 | import React from 'react'
53 | import { useStore } from 'stook'
54 | import { submitUser } from './user.service.ts'
55 |
56 | export const UserForm = () => {
57 | const [name, setName] = useStore('[UserForm]', 'initial name')
58 | return (
59 |
60 | setName(e.target.value)} />
61 |
62 |
63 | )
64 | }
65 | ```
66 |
67 | ## 在组件内读取 state
68 |
69 | 有时,由于某些特殊原因,我们可能会在组件内单纯地读取 state:
70 |
71 | ```jsx
72 | export const User = () => {
73 | const name = getState('[UserName]')
74 | return (
75 |
76 | {name}
77 |
78 | )
79 | }
80 | ```
81 |
--------------------------------------------------------------------------------
/website/docs/stook/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: Introduction
4 | sidebar_label: Introduction
5 | ---
6 |
7 | ## What is Stook?
8 |
9 | Stook 是一系列基于 React hooks 的工具,它的核心一个基于 hooks 实现的状态管理工具。
10 |
11 | ## motivation
12 |
13 | 我喜欢 React,一年来,我一直在探索如何更简单的管理 React 应用状态,对于我而言,一个状态管理库的最重要的两点是:1.简单;2.完美支持 TypeScript 。很抱歉,React 生态中的王者 Redux 没有满足这两点中的任何一点。自从 React Hooks 出现,我发现基于 Hooks 的状态管理可以非常简单,并且完美支持 TypeScript,所以会有了 [stook](https://github.com/forsigner/stook)。它的名字由 store 和 hook 两个单词组合而成。
14 |
15 | ## Why use Stook?
16 |
17 | - **Minimalism** 核心 Api 几乎和 `useState`用法一样,极低的学习成本.
18 |
19 | - **Hooks** 基于 hooks 实现,符合 React 的发展趋势.
20 |
21 | - **TypeScript** TypeScipt 支持非常完美.
22 |
23 | - **Extensible**扩展性强,使用起来非常灵活.
24 |
25 | ## More than state Management
26 |
27 | 但它不仅仅是一个状态管理工具,它衍生出了一个基于 stook 的生态体系,让你用 hooks 的思维开发 React 应用。
28 |
29 | 比如下面这些工具:
30 |
31 | - [stook-rest](/docs/rest/intro)
32 | - [stook-graphql](/docs/graphql/intro)
33 | - [entity-form](https://github.com/forsigner/entity-form)
34 |
35 | more...
36 |
--------------------------------------------------------------------------------
/website/docs/stook/mutate.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: mutate
3 | title: mutate
4 | sidebar_label: mutate
5 | ---
6 |
7 | `mutate` 用来改变一个 store 的 state,它的用法类似 `[state setState] = useStore(key)` 中的 setState,但需要传入某个 store 的 key,强大地方是它可以在任何地方使用。
8 |
9 | ```js
10 | // 直接 replace
11 | mutate('User', { id: 1, name: 'foo' })
12 |
13 | //or use function
14 | mutate('User', state => ({
15 | ...state,
16 | name: 'foo',
17 | }))
18 |
19 | //or use immer
20 | mutate('User', state => {
21 | state.name = 'foo'
22 | })
23 | ```
24 |
25 | mutate 你可以初始化一个**不存在的** store,这是一个特殊的使用场景。
26 |
27 | 举个例子,你可以用 mutate 初始化一个用户的 store:
28 |
29 | ```jsx
30 | const user = localStorage.getItem('User') // { id: 1, name: 'foo' }
31 | mutate('User', user)
32 | ```
33 |
34 | 然后在组件使用 (不需要再初始化):
35 |
36 | ```jsx
37 | const Profile = () => {
38 | const [user, updateUser] = useStore('User')
39 | return (
40 |
41 | {user.name}
42 |
43 |
44 | )
45 | }
46 | ```
47 |
48 | [](https://codesandbox.io/s/broken-snow-7k6r8?fontsize=14&hidenavigation=1&theme=dark)
49 |
--------------------------------------------------------------------------------
/website/docs/stook/quick-start.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: quick-start
3 | title: Quick start
4 | sidebar_label: Quick start
5 | ---
6 |
7 | ## Installation
8 |
9 | ### Install with npm
10 |
11 | ```bash
12 | npm install stook
13 | ```
14 |
15 | ### Install with yarn
16 |
17 | ```bash
18 | yarn add stook
19 | ```
20 |
21 | ## Usage
22 |
23 | 下面是一个经典的 Counter 组件,展示了 `stook` 的最基本用法:
24 |
25 | ```tsx
26 | import React from 'react'
27 | import { useStore } from 'stook'
28 |
29 | function Counter() {
30 | const [count, setCount] = useStore('Counter', 0)
31 | return (
32 |
33 |
You clicked {count} times
34 |
35 |
36 | )
37 | }
38 | ```
39 |
40 | [](https://codesandbox.io/s/ancient-night-gyres?fontsize=14&hidenavigation=1&theme=dark)
41 |
42 | `stook` 最核心的 Api 就是 `useStore`,也许你发现了,它和 `useState` 非常像,实际上,`useStore` 除了多了一个参数以外,其他用法一模一样。
43 |
--------------------------------------------------------------------------------
/website/docs/stook/share-state.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: share-state
3 | title: Share state
4 | sidebar_label: Share state
5 | ---
6 |
7 | 对于状态管理,最核心的功能就是状态的跨组件通信。useState 用于管理单一组件内的状态,useStore 则可以跨组件管理整个应用的状态。
8 |
9 | 下面展示了如何多个组件如何共享状态:
10 |
11 | ```jsx
12 | import React from 'react'
13 | import { useStore } from 'stook'
14 |
15 | function Counter() {
16 | const [count, setCount] = useStore('Counter', 0)
17 | return (
18 |
19 |
You clicked {count} times
20 |
21 |
22 | )
23 | }
24 |
25 | function Display() {
26 | const [count] = useStore('Counter')
27 | return {count}
28 | }
29 |
30 | function App() {
31 | return (
32 |
33 |
34 |
35 |
36 | )
37 | }
38 | ```
39 |
40 | [](https://codesandbox.io/s/vibrant-swirles-jw7kw?fontsize=14&hidenavigation=1&theme=dark)
41 |
42 | 在这个例子中,我们可以看到,要共享状态,只需使用 useStore 订阅同一个 key 即可,非常简单。可以说,只要你学会了 useState,也就学会了 useStore,只要你学会了 useStore,你就学会了 React 的状态管理。
43 |
--------------------------------------------------------------------------------
/website/docs/stook/test.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: test
3 | title: Test
4 | sidebar_label: Test
5 | ---
6 |
7 |
8 | 测试 stook 是一件非常简单的事,因为测试 stook 也就是在测试 react hooks。
9 |
10 | 推荐使用 [react-hooks-testing-library](https://react-hooks-testing-library.com/)工具来测试 stook。
11 |
12 | ## 安装依赖
13 |
14 | ```bash
15 | npm install -D @testing-library/react-hooks react-test-renderer
16 | ```
17 |
18 | ## 例子
19 |
20 | **`useCounter.ts`**
21 |
22 | ```js
23 | function useCounter() {
24 | const [count, setCount] = useStore('Counter', 0)
25 | const decrease = () => setCount(count - 1)
26 | const increase = () => setCount(count + 1)
27 | return { count, increase, decrease }
28 | }
29 | ```
30 |
31 | **`useCounter.test.ts`**
32 |
33 | ```js
34 | import { renderHook, act } from '@testing-library/react-hooks'
35 | import useCounter from './useCounter'
36 |
37 | test('should increment counter', () => {
38 | const { result } = renderHook(() => useCounter())
39 |
40 | act(() => {
41 | result.current.increase()
42 | })
43 |
44 | expect(result.current.count).toBe(1)
45 | })
46 | ```
47 |
48 | 更多的测试技巧请看:[react-hooks-testing-library](https://react-hooks-testing-library.com/)。
49 |
--------------------------------------------------------------------------------
/website/docs/stook/typescript.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: typescript
3 | title: Typescript
4 | sidebar_label: Typescript
5 | ---
6 |
7 | 我是 TypeScript 的忠实拥泵,创建 Stook 的初衷之一这就是完美地支持 TypeScript。得益于 React Hooks 对 TypeScript 完备的支持,Stook 也完美地支持 TypeScript。
8 |
9 | ## Store key
10 |
11 | ```Stook``` 的核心 Api 只有3个:useStore、getState、mutate,它们的共同点之一就是第一个参数是 store 的唯一key,默认 key 可以是任何字符串,如果我们想在编辑器中获得智能提示,我们可以这样去实现:
12 |
13 | 在项目根目录新建文件 ```index.d.ts```,添加类似下面内容:
14 | ```ts
15 | import stook from 'stook'
16 |
17 | declare module 'stook' {
18 | interface Key {
19 | User: string
20 | Counter: string
21 | }
22 | }
23 | ```
24 | 这样你使用 useStore、getState、mutate 就可以获得智能提示。原理其实就是用了 TypeScript 的 [declaration-merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html)。
25 |
26 |
27 | ## useStore
28 |
29 | 和 `useState` 一样,`useStore` 能根据 initialState 推导 state 的类型:
30 |
31 | ```jsx
32 | const [user, setUser] = useStore('User', { id: 1, name: 'foo' })
33 | ```
34 |
35 | 上面代码会自动推导出 user 的类型为:
36 |
37 | ```ts
38 | interface User {
39 | id: number
40 | name: string
41 | }
42 | ```
43 |
44 | 当然,你也可以通过泛型显示地声明类型:
45 |
46 | ```ts
47 | interface User {
48 | id: number
49 | name: string
50 | }
51 |
52 | const [user, setUser] = useStore('User', { id: 1, name: 'foo' })
53 | ```
54 |
55 | ## mutate
56 |
57 | 使用泛型声明 mutate 的类型:
58 |
59 | ```ts
60 | interface User {
61 | id: number
62 | name: string
63 | }
64 |
65 | mutate('User', user => {
66 | user.name = 'bar'
67 | })
68 | ```
69 |
70 | ## getState
71 |
72 | 使用泛型声明 getState 的类型:
73 |
74 | ```ts
75 | interface User {
76 | id: number
77 | name: string
78 | }
79 |
80 | const user = getState('User')
81 | ```
82 |
--------------------------------------------------------------------------------
/website/docs/stook/use-store.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: use-store
3 | title: useStore
4 | sidebar_label: useStore
5 | ---
6 |
7 | `const [state, setState] = useStore(key, initialState)`
8 |
9 | `useStore` 是 stook 的核心 Api,上面的例子展示它的基本用法。实际上,和 `useState` 相比, `useStore` 除了多了一个参数以外,其他用法一模一样,不同的是他们实现的效果:`useState` 的状态是局部的,`useStore` 的状态全局的。
10 |
11 | 还一个不同的是,`useStore` 内置了 immer,所以你可以有以下的用法:
12 |
13 | ```jsx
14 | const [user, setUser] = useStore('User', { id: 1, name: 'foo' })
15 |
16 | setUser(state => {
17 | state.name = 'bar'
18 | })
19 | ```
20 |
21 | 还一个指得注意的是,如果你对同一个 key 进行了 initialState 的初始化,stook 只会使用第一个 initialState。
22 |
23 | 在某个组件初始化了 `User` store:
24 |
25 | ```jsx
26 | // component A
27 | const [user, setUser] = useStore('User', { id: 1, name: 'foo' })
28 | ```
29 |
30 | 后续,如果再初始化 `User` store 是无效的,因为这个 `User` store 已经初始化过了,所以这时你不用再传 initialState 参数。
31 |
32 | ```jsx
33 | // component B
34 | const [user, setUser] = useStore('User', { id: 1, name: 'bar' }) // bad
35 |
36 | const [user, setUser] = useStore('User') // good,直接读取 store
37 | ```
38 |
39 | 如果你提前使用 mutate 初始化过一个 store,后续 `useStore` 一样不用再传 initialState 参数。
40 |
--------------------------------------------------------------------------------
/website/docusaurus.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@docusaurus/types').DocusaurusConfig} */
2 | module.exports = {
3 | title: 'Stook',
4 | tagline: 'A minimalist design state management library for React',
5 | url: 'https://your-docusaurus-test-site.com',
6 | baseUrl: '/',
7 | onBrokenLinks: 'throw',
8 | onBrokenMarkdownLinks: 'warn',
9 | favicon: 'img/favicon.ico',
10 | organizationName: 'forsigner', // Usually your GitHub org/user name.
11 | projectName: 'stook',
12 | themeConfig: {
13 | navbar: {
14 | title: 'Stook',
15 | items: [
16 | {
17 | type: 'doc',
18 | docId: 'stook/intro',
19 | position: 'left',
20 | label: 'Docs',
21 | },
22 |
23 | { to: '/ecosystem', label: 'Ecosystem', position: 'right' },
24 |
25 | {
26 | href: 'https://www.github.com/forsigner/stook',
27 | label: 'GitHub',
28 | position: 'right',
29 | },
30 | ],
31 | },
32 | footer: {
33 | style: 'dark',
34 | links: [
35 | {
36 | title: '文档',
37 | items: [
38 | {
39 | label: 'Quick Start',
40 | to: '/docs/stook/quick-start',
41 | },
42 | {
43 | label: 'Stook',
44 | to: '/docs/stook/intro',
45 | },
46 | {
47 | label: 'GraphQL',
48 | to: '/docs/graphql/intro',
49 | },
50 | ],
51 | },
52 | {
53 | title: '社区',
54 | items: [
55 | {
56 | label: 'Stack Overflow',
57 | href: 'https://stackoverflow.com/questions/tagged/stook',
58 | },
59 | {
60 | label: 'Feedback',
61 | to: 'https://github.com/forsigner/stook/issues',
62 | },
63 | ],
64 | },
65 | {
66 | title: '社交',
67 | items: [
68 | {
69 | label: 'GitHub',
70 | href: 'https://github.com/forsigner/stook',
71 | },
72 | ],
73 | },
74 | ],
75 | copyright: `Copyright © ${new Date().getFullYear()} forsigner.`,
76 | },
77 | },
78 | presets: [
79 | [
80 | '@docusaurus/preset-classic',
81 | {
82 | docs: {
83 | sidebarPath: require.resolve('./sidebars.js'),
84 | // Please change this to your repo.
85 | editUrl: 'https://github.com/facebook/docusaurus/edit/master/website/',
86 | },
87 | blog: {
88 | showReadingTime: true,
89 | // Please change this to your repo.
90 | editUrl: 'https://github.com/facebook/docusaurus/edit/master/website/blog/',
91 | },
92 | theme: {
93 | customCss: require.resolve('./src/css/custom.css'),
94 | },
95 | },
96 | ],
97 | ],
98 | }
99 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "website",
3 | "version": "1.15.0",
4 | "private": true,
5 | "scripts": {
6 | "docusaurus": "docusaurus",
7 | "start": "docusaurus start",
8 | "build": "echo",
9 | "build:prod": "docusaurus build",
10 | "swizzle": "docusaurus swizzle",
11 | "deploy": "docusaurus deploy",
12 | "clear": "docusaurus clear",
13 | "serve": "docusaurus serve",
14 | "write-translations": "docusaurus write-translations",
15 | "write-heading-ids": "docusaurus write-heading-ids"
16 | },
17 | "dependencies": {
18 | "@docusaurus/core": "2.0.0-alpha.73",
19 | "@docusaurus/preset-classic": "2.0.0-alpha.73",
20 | "@fower/react": "^1.4.0",
21 | "@mdx-js/react": "^1.6.21",
22 | "clsx": "^1.1.1",
23 | "react": "^17.0.1",
24 | "react-dom": "^17.0.1",
25 | "react-live": "^2.2.3",
26 | "styled-components": "^5.2.3"
27 | },
28 | "browserslist": {
29 | "production": [
30 | ">0.5%",
31 | "not dead",
32 | "not op_mini all"
33 | ],
34 | "development": [
35 | "last 1 chrome version",
36 | "last 1 firefox version",
37 | "last 1 safari version"
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/website/sidebars.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creating a sidebar enables you to:
3 | - create an ordered group of docs
4 | - render a sidebar for each doc of that group
5 | - provide next/previous navigation
6 |
7 | The sidebars can be generated from the filesystem, or explicitly defined here.
8 |
9 | Create as many sidebars as you want.
10 | */
11 |
12 | module.exports = {
13 | someSidebar: {
14 | stook: [
15 | 'stook/intro',
16 | 'stook/quick-start',
17 | 'stook/share-state',
18 | 'stook/custom-hooks',
19 | 'stook/use-store',
20 | 'stook/mutate',
21 | 'stook/get-state',
22 | 'stook/typescript',
23 | 'stook/test',
24 | 'stook/faq',
25 | ],
26 |
27 | 'stook-devtools': ['devtools/intro'],
28 |
29 | 'stook-rest': [
30 | 'rest/intro',
31 | 'rest/quick-start',
32 | 'rest/config',
33 | 'rest/useFetch',
34 | 'rest/useUpdate',
35 | 'rest/dependent',
36 | 'rest/share-state',
37 | 'rest/custom-hooks',
38 | 'rest/fetch',
39 | 'rest/refetch',
40 | 'rest/middleware',
41 | ],
42 | 'stook-graphql': [
43 | 'graphql/intro',
44 | 'graphql/quick-start',
45 | 'graphql/config',
46 | 'graphql/useQuery',
47 | 'graphql/useMutation',
48 | 'graphql/query',
49 | 'graphql/middleware',
50 | ],
51 | },
52 | }
53 |
--------------------------------------------------------------------------------
/website/src/components/UserForm.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default () => {
4 | return user form
5 | }
6 |
--------------------------------------------------------------------------------
/website/src/css/custom.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable docusaurus/copyright-header */
2 | /**
3 | * Any CSS included here will be global. The classic template
4 | * bundles Infima by default. Infima is a CSS framework designed to
5 | * work well for content-centric websites.
6 | */
7 |
8 | /* You can override the default Infima variables here. */
9 | :root {
10 | --ifm-color-primary: #25c2a0;
11 | --ifm-color-primary-dark: rgb(33, 175, 144);
12 | --ifm-color-primary-darker: rgb(31, 165, 136);
13 | --ifm-color-primary-darkest: rgb(26, 136, 112);
14 | --ifm-color-primary-light: rgb(70, 203, 174);
15 | --ifm-color-primary-lighter: rgb(102, 212, 189);
16 | --ifm-color-primary-lightest: rgb(146, 224, 208);
17 | --ifm-code-font-size: 95%;
18 | }
19 |
20 | .docusaurus-highlight-code-line {
21 | background-color: rgb(72, 77, 91);
22 | display: block;
23 | margin: 0 calc(-1 * var(--ifm-pre-padding));
24 | padding: 0 var(--ifm-pre-padding);
25 | }
26 |
--------------------------------------------------------------------------------
/website/src/pages/ecosystem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import React from 'react'
9 | import classnames from 'classnames'
10 | import Layout from '@theme/Layout'
11 | import Link from '@docusaurus/Link'
12 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
13 | import styled from 'styled-components'
14 |
15 | const Main = styled.div`
16 | margin: 30px auto 0;
17 | width: 760px;
18 | `
19 |
20 | const List = styled.ul`
21 | list-style: none;
22 | `
23 |
24 | const Item = styled.li`
25 | position: relative;
26 | /* box-shadow: 1px 5px 10px #f0f0f0; */
27 | border: 1px solid #f0f0f0;
28 | color: #586069;
29 | border-radius: 10px;
30 | padding: 10px;
31 | margin: 10px 0;
32 | `
33 |
34 | const Btn = styled.a`
35 | position: absolute;
36 | top: 10px;
37 | right: 10px;
38 | display: inline-block;
39 | padding: 0 15px;
40 | box-sizing: border-box;
41 | text-align: center;
42 | height: 30px;
43 | line-height: 30px;
44 | font-size: 12px;
45 | border-radius: 15px;
46 | border: 1px solid #eaeaea;
47 | &:hover {
48 | text-decoration: none;
49 | }
50 | `
51 |
52 | const Name = styled.div`
53 | font-size: 24px;
54 | margin-top: 5px;
55 | `
56 |
57 | const Author = styled.div`
58 | color: #aaa;
59 | font-size: 12px;
60 | margin-top: 5px;
61 | `
62 |
63 | const list = [
64 | {
65 | name: 'stook-rest',
66 | intro: 'React Hooks to Restful Api',
67 | github: 'https://github.com/forsigner/stook/blob/master/packages/stook-rest/README.md',
68 | author: 'forsigner',
69 | tag: ['graphql', 'stook', 'useQuery'],
70 | },
71 | {
72 | name: 'stook-graphql',
73 | intro: 'React Hooks to query GraphQL',
74 | github: 'https://github.com/forsigner/stook/blob/master/packages/stook-graphql/README.md',
75 | author: 'stook',
76 | tag: ['graphql', 'stook', 'useQuery'],
77 | },
78 | {
79 | name: 'entity-form',
80 | intro: 'React form base on Hooks',
81 | github: 'https://github.com/forsigner/entity-form',
82 | author: 'forsigner',
83 | tag: ['form', 'stook'],
84 | },
85 | ]
86 |
87 | function Home() {
88 | const context = useDocusaurusContext()
89 | const { siteConfig = {} } = context
90 | return (
91 |
95 |
96 |
97 | {list.map(item => (
98 | -
99 |
100 | {item.name}
101 |
102 |
{item.intro}
103 | built by {item.author}
104 |
105 | Github
106 |
107 |
108 | ))}
109 |
110 |
111 |
112 | )
113 | }
114 |
115 | export default Home
116 |
--------------------------------------------------------------------------------
/website/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import clsx from 'clsx'
3 | import Layout from '@theme/Layout'
4 | import DLink from '@docusaurus/Link'
5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
6 | import useBaseUrl from '@docusaurus/useBaseUrl'
7 | import styles from './styles.module.css'
8 | import Translate from '@docusaurus/Translate'
9 | import { Box } from '@fower/react'
10 | import theme from 'prism-react-renderer/themes/duotoneDark'
11 |
12 | import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live'
13 | import { styled } from '@fower/styled'
14 |
15 | const Link = styled(DLink)
16 |
17 | const code1 = `
18 |
19 |
20 |
21 |
22 | `
23 |
24 | const features = [
25 | {
26 | title: 'Minimalism',
27 | description: '核心 Api 几乎和 `useState`用法一样,极低的学习成本.',
28 | },
29 | {
30 | title: 'Hooks',
31 | description: '基于 hooks 实现,符合 React 的发展趋势.',
32 | },
33 |
34 | {
35 | title: 'TypeScript',
36 | description: 'TypeScipt 支持非常完美.',
37 | },
38 | ]
39 |
40 | function Feature({ title, description, idx }) {
41 | return (
42 |
49 |
{title}
50 |
{description}
51 |
52 | )
53 | }
54 |
55 | function Home() {
56 | const context = useDocusaurusContext()
57 | const { siteConfig = {}, tagline } = context
58 | return (
59 |
60 |
61 |
62 |
63 |
64 | A minimalist design state management library for React.
65 |
66 |
67 |
68 |
80 | Getting Start
81 |
82 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | {features && features.length > 0 && (
96 |
97 |
98 |
99 | {features.map((props, idx) => (
100 |
101 | ))}
102 |
103 |
104 |
105 | )}
106 |
107 |
108 | )
109 | }
110 |
111 | export default Home
112 |
--------------------------------------------------------------------------------
/website/src/pages/styles.module.css:
--------------------------------------------------------------------------------
1 | @media screen and (max-width: 966px) {
2 | .heroBanner {
3 | padding: 2rem;
4 | }
5 | }
6 |
7 | .featureImage {
8 | height: 200px;
9 | width: 200px;
10 | }
11 |
12 | .indexCtasGitHubButton {
13 | border: none;
14 | margin-left: 24px;
15 | overflow: hidden;
16 | }
17 |
18 | .container {
19 | max-width: 1360px;
20 | display: flex;
21 | align-items: center;
22 | justify-content: center;
23 | flex-direction: column;
24 | text-align: center;
25 | margin: 0 auto 40px;
26 | padding: 60px 0 !important;
27 | }
28 |
29 |
30 | .red {
31 | /* color: #fc8181; */
32 | color: #25c2a0;
33 | }
34 |
35 | .green {
36 | /* color: var(--ifm-color-primary); */
37 | color: #61dafb;
38 | }
39 |
40 |
41 | .wrapLink {
42 | display: flex;
43 | align-items: center;
44 | margin-top: 20px;
45 | }
46 |
47 | .item {
48 | display: flex;
49 | align-items: center;
50 | justify-content: center;
51 | margin-top: 20px;
52 | /* padding: 0 2rem; */
53 | margin-bottom: 40px;
54 | }
55 |
56 | /* .getStarted:hover {
57 | background-color: #333 !important;
58 | } */
--------------------------------------------------------------------------------
/website/static/fonts/calligraffitti-regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forsigner/stook/04e494ca2cd84f1fe5f7b48aa40b3047175ab9b0/website/static/fonts/calligraffitti-regular-webfont.eot
--------------------------------------------------------------------------------
/website/static/fonts/calligraffitti-regular-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forsigner/stook/04e494ca2cd84f1fe5f7b48aa40b3047175ab9b0/website/static/fonts/calligraffitti-regular-webfont.ttf
--------------------------------------------------------------------------------
/website/static/fonts/calligraffitti-regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forsigner/stook/04e494ca2cd84f1fe5f7b48aa40b3047175ab9b0/website/static/fonts/calligraffitti-regular-webfont.woff
--------------------------------------------------------------------------------
/website/static/fonts/calligraffitti-regular-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forsigner/stook/04e494ca2cd84f1fe5f7b48aa40b3047175ab9b0/website/static/fonts/calligraffitti-regular-webfont.woff2
--------------------------------------------------------------------------------
/website/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forsigner/stook/04e494ca2cd84f1fe5f7b48aa40b3047175ab9b0/website/static/img/favicon.ico
--------------------------------------------------------------------------------
/website/static/img/github-brands.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/static/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forsigner/stook/04e494ca2cd84f1fe5f7b48aa40b3047175ab9b0/website/static/img/logo.png
--------------------------------------------------------------------------------
/website/static/img/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/static/img/stook-devtools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forsigner/stook/04e494ca2cd84f1fe5f7b48aa40b3047175ab9b0/website/static/img/stook-devtools.png
--------------------------------------------------------------------------------
/website/static/img/use-store.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forsigner/stook/04e494ca2cd84f1fe5f7b48aa40b3047175ab9b0/website/static/img/use-store.png
--------------------------------------------------------------------------------
/website/static/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "alias": ["stook-cn"]
4 | }
5 |
--------------------------------------------------------------------------------