├── test ├── hooks │ ├── memo.tsx │ └── state.tsx ├── index.ts ├── inputs.ts ├── context.ts └── fake-hooks-component.ts ├── .gitignore ├── dist ├── react-hooks.min.js.map └── react-hooks.min.js ├── tsconfig.json ├── tslint.json ├── src ├── index.ts ├── inputs.ts ├── context.ts ├── hooks │ ├── memo.ts │ ├── context.tsx │ ├── ref.ts │ ├── effect.ts │ └── state.ts └── hooks-component.ts ├── rollup.config.js ├── package.json ├── README.md └── example.html /test/hooks/memo.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | .rpt2_cache 4 | _trash -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | import './inputs' 2 | import './context' 3 | import './hooks/state' 4 | import './hooks/memo' -------------------------------------------------------------------------------- /dist/react-hooks.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"react-hooks.min.js","sources":[],"sourcesContent":[],"names":[],"mappings":""} -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "jsx": "react", 5 | "strict": true 6 | }, 7 | "include": [ 8 | "src/**.tsx", 9 | "src/**.ts", 10 | "test/**.ts", 11 | "test/**.tsx", 12 | ] 13 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-react", 5 | "tslint-config-prettier" 6 | ], 7 | "linterOptions": { 8 | "exclude": [ 9 | "config/**/*.js", 10 | "node_modules/**/*.ts", 11 | "coverage/lcov-report/*.js" 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { withHooks } from './hooks-component' 2 | export { useState, useReducer } from './hooks/state' 3 | export { useEffect, useLayoutEffect } from './hooks/effect' 4 | export { useContext } from './hooks/context' 5 | export { useRef, useImperativeMethods } from './hooks/ref' 6 | export { useMemo, useCallback } from './hooks/memo' -------------------------------------------------------------------------------- /test/inputs.ts: -------------------------------------------------------------------------------- 1 | import { inputsChange } from "../src/inputs"; 2 | import * as assert from 'assert' 3 | 4 | assert.equal(inputsChange(undefined, undefined), true) 5 | assert.equal(inputsChange([], []), false) 6 | 7 | assert.equal(inputsChange([1], ['1']), true) 8 | assert.equal(inputsChange([1], [1, 2]), true) 9 | 10 | const obj = {} 11 | assert.equal(inputsChange([obj], [obj]), false) 12 | assert.equal(inputsChange([1, 2, 3], [1, 2, 3]), false) -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import buble from 'rollup-plugin-buble' 2 | const {uglify} = require('rollup-plugin-uglify') 3 | import typescript from 'rollup-plugin-typescript2' 4 | 5 | export default { 6 | input: 'src/index.ts', 7 | output: { 8 | file: 'dist/react-hooks.min.js', 9 | format: 'umd', 10 | name: 'ReactHooks', 11 | sourcemap: true, 12 | }, 13 | external: ['react'], 14 | plugins: [ 15 | typescript(), 16 | buble(), 17 | uglify() 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/inputs.ts: -------------------------------------------------------------------------------- 1 | 2 | export type HooksInputs = any[] | undefined 3 | 4 | export function inputsChange(oldInputs: HooksInputs, newInputs: HooksInputs) { 5 | if (oldInputs && newInputs) { 6 | if (oldInputs.length > 0 && newInputs.length > 0) { 7 | if (oldInputs.length === newInputs.length) { 8 | for (let i = 0, l = oldInputs.length; i < l; i++) { 9 | if (oldInputs[i] !== newInputs[i]) { 10 | return true 11 | } 12 | } 13 | } else { 14 | return true 15 | } 16 | } 17 | return false 18 | } 19 | return true 20 | } 21 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | import {HooksComponent} from './hooks-component' 2 | 3 | interface HooksContext { 4 | component: HooksComponent, 5 | counter: number 6 | } 7 | 8 | const hooksContextStack: HooksContext[] = [] 9 | 10 | export function useCounter() { 11 | const context = hooksContextStack[hooksContextStack.length - 1] 12 | const counter = context.counter++ 13 | return { component: context.component, counter } 14 | } 15 | 16 | export function withContext(component: HooksComponent, func: (...args: Args) => Ret) 17 | : (...args: Args) => Ret { 18 | return function (...args) { 19 | hooksContextStack.push({ component, counter: 0 }) 20 | const result = func(...args) 21 | hooksContextStack.pop() 22 | return result 23 | } 24 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blx-react-hooks", 3 | "version": "0.0.7", 4 | "main": "dist/react-hooks.min.js", 5 | "types": "src/index.ts", 6 | "repository": "git@github.com:bramblex/react-hooks.git", 7 | "author": "乔健 ", 8 | "license": "MIT", 9 | "scripts": { 10 | "build": "rollup -c", 11 | "lint": "tslint", 12 | "test": "ts-node test/index.ts" 13 | }, 14 | "dependencies": { 15 | "react": "^16.6.3", 16 | "react-dom": "^16.6.3" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^10.12.11", 20 | "@types/react": "^16.7.6", 21 | "rollup": "^0.66.6", 22 | "rollup-plugin-buble": "^0.19.4", 23 | "rollup-plugin-commonjs": "^9.2.0", 24 | "rollup-plugin-node-resolve": "^3.4.0", 25 | "rollup-plugin-replace": "^2.1.0", 26 | "rollup-plugin-typescript2": "^0.17.2", 27 | "rollup-plugin-uglify": "^6.0.0", 28 | "ts-node": "^7.0.1", 29 | "tslint": "^5.11.0", 30 | "typescript": "^3.1.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/hooks/memo.ts: -------------------------------------------------------------------------------- 1 | import { useCounter } from "../context"; 2 | import { HooksInputs, inputsChange } from "../inputs"; 3 | 4 | export interface HooksComponentMemos { 5 | [counter: number]: [any, HooksInputs | undefined] 6 | } 7 | 8 | export function useMemo(computedFunc: () => T, inputs?: HooksInputs): T { 9 | const { component, counter } = useCounter() 10 | const componentMemos: { [counter: number]: [T, HooksInputs | undefined] } 11 | = component.__hooks__.memos 12 | 13 | if (!componentMemos.hasOwnProperty(counter)) { 14 | componentMemos[counter] = [computedFunc(), inputs] 15 | } else { 16 | const [, oldInputs] = componentMemos[counter] 17 | if (inputsChange(oldInputs, inputs)) { 18 | componentMemos[counter] = [computedFunc(), inputs] 19 | } 20 | } 21 | return componentMemos[counter][0] 22 | } 23 | 24 | export function useCallback(callback: (...args: Args) => Ret, inputs?: HooksInputs): (...args: Args) => Ret { 25 | return useMemo(() => callback, inputs) 26 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-hooks 2 | 3 | React Hooks API 在低版本 React 16.6 下面的一个实现,可以在不升级 React 版本的前提下体验新的 React Hooks API。 4 | ## Install 5 | 6 | ``` 7 | npm install blx-react-hooks 8 | # or 9 | yarn add blx-react-hooks 10 | ``` 11 | 12 | ## Usage 13 | 在声明函数式组件的时候,通过用 `withHooks()` 函数封装就可以就使用 Hooks API 了。与 React 的 Hooks API 只差了需要 `withHooks()` 函数进行封装。 14 | 15 | ```JSX 16 | import {withHooks, useState} from 'blx-react-hooks' 17 | 18 | // 如果想在调试的时候看到组件名,定义function的时候必须要定义成一个命名函数 19 | const Counter = withHooks(function Counter({title, initCount}) { 20 | const [count, setCount] = useState(initCount) 21 | return ( 22 |
23 |

{title}

24 | 25 | Count: {count} 26 | 27 |
28 | ) 29 | }) 30 | ``` 31 | 32 | 完整示例 [https://github.com/bramblex/react-hooks/blob/master/example.html](https://github.com/bramblex/react-hooks/blob/master/example.html) 33 | 34 | ## API 35 | 36 | * withHooks 37 | * useState / useReducer 38 | * useEffect / useLayoutEffect 39 | * useContext 40 | * useRef / useImperativeMethods 41 | * useMemo / useCallback 42 | 43 | 44 | ## Document 45 | API 的文档直接参见 React Hooks API 的文档:[https://reactjs.org/docs/hooks-intro.html](https://reactjs.org/docs/hooks-intro.html) 46 | -------------------------------------------------------------------------------- /src/hooks/context.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { useCounter } from "../context" 3 | 4 | export interface HooksComponentContexts { 5 | [counter: number]: [any, React.Context] 6 | } 7 | 8 | export function bindContexts(contexts: HooksComponentContexts, renderFunc: () => React.ReactNode): () => React.ReactNode { 9 | renderFunc() 10 | 11 | const contextsArray = Object 12 | .getOwnPropertyNames(contexts) 13 | .map(_counter => { 14 | const counter: number = parseInt(_counter) 15 | return [counter, contexts[counter][1]] as [number, React.Context] 16 | }) 17 | 18 | if (contextsArray.length <= 0) { 19 | return renderFunc 20 | } else { 21 | return contextsArray.reduceRight((lastRenderFunc, [counter, context]) => () => ( 22 | 23 | {value => { 24 | contexts[counter] = [value, context] 25 | return lastRenderFunc() 26 | }} 27 | 28 | ), renderFunc) 29 | } 30 | } 31 | 32 | export function useContext(context: React.Context): T { 33 | const { component, counter } = useCounter() 34 | const componentContexts = component.__hooks__.contexts 35 | if (!componentContexts.hasOwnProperty(counter)) { 36 | componentContexts[counter] = [(context as any)._currentValue, context] 37 | } 38 | return componentContexts[counter][0] 39 | } -------------------------------------------------------------------------------- /test/context.ts: -------------------------------------------------------------------------------- 1 | 2 | import { withContext, useCounter } from '../src/context' 3 | import * as assert from 'assert' 4 | 5 | const fakeComponent1 = {} as any 6 | const func1 = withContext(fakeComponent1, function func1() { 7 | const { component: component1, counter: counter1 } = useCounter() 8 | assert(component1 === fakeComponent1) 9 | assert(counter1 === 0) 10 | const { component: component2, counter: counter2 } = useCounter() 11 | assert(component2 === fakeComponent1) 12 | assert(counter2 === 1) 13 | }) 14 | 15 | 16 | const fakeComponent2 = {} as any 17 | const func2 = withContext(fakeComponent2, function func2() { 18 | const { component: component1, counter: counter1 } = useCounter() 19 | assert(component1 === fakeComponent2) 20 | assert(counter1 === 0) 21 | 22 | func1() 23 | func1() 24 | 25 | const { component: component2, counter: counter2 } = useCounter() 26 | assert(component2 === fakeComponent2) 27 | assert(counter2 === 1) 28 | }) 29 | 30 | 31 | const fakeComponent3 = {} as any 32 | const func3 = withContext(fakeComponent3, function func3() { 33 | const { component: component1, counter: counter1 } = useCounter() 34 | assert(component1 === fakeComponent3) 35 | assert(counter1 === 0) 36 | 37 | func1() 38 | func2() 39 | 40 | const { component: component2, counter: counter2 } = useCounter() 41 | assert(component2 === fakeComponent3) 42 | assert(counter2 === 1) 43 | }) 44 | 45 | func3() 46 | func3() 47 | func3() -------------------------------------------------------------------------------- /test/hooks/state.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { fakeWithHooks } from '../fake-hooks-component' 3 | import { useState, useReducer } from '../../src/hooks/state'; 4 | import * as assert from 'assert' 5 | import { useEffect } from '../../src/hooks/effect'; 6 | 7 | const FakeComponent1 = fakeWithHooks(function FakeComponent() { 8 | const [count] = useState(0) 9 | const [count2] = useState(1) 10 | return null 11 | }) 12 | 13 | const fakeComponent1 = new FakeComponent1({}) 14 | assert.deepStrictEqual(fakeComponent1.state, { 0: 0, 1: 1 }) 15 | 16 | const FakeComponent2 = fakeWithHooks(function FakeComponent() { 17 | const [count, setCount] = useState(0) 18 | const [count2, setCount2] = useState(1) 19 | useEffect(() => { 20 | setCount(count + 1) 21 | setCount2(count2 + 2) 22 | }) 23 | return null 24 | }) 25 | 26 | const fakeComponent2 = new FakeComponent2({}); 27 | (fakeComponent2 as any).componentDidMount() 28 | assert.deepStrictEqual(fakeComponent2.state, { 0: 1, 1: 3 }) 29 | 30 | 31 | function reducer(state: number, action: { type: string, payload?: any }) { 32 | if (action.type === 'init') { 33 | return 1 34 | } else if (action.type === 'add') { 35 | return state + action.payload 36 | } 37 | return state 38 | } 39 | 40 | const FakeComponent3 = fakeWithHooks(function FakeComponent() { 41 | const [state, dispatch] = useReducer(reducer, 0) 42 | const [state2, dispatch2] = useReducer(reducer, 0, { type: 'init' }) 43 | useEffect(() => { 44 | dispatch({ type: 'add', payload: 2 }) 45 | }) 46 | return null 47 | }) 48 | 49 | const fakeComponent3 = new FakeComponent3({}); 50 | assert.deepStrictEqual(fakeComponent3.state, { 0: 0, 1: 1 }); 51 | (fakeComponent3 as any).componentDidMount() 52 | assert.deepStrictEqual(fakeComponent3.state, { 0: 2, 1: 1 }) 53 | fakeComponent3.render(); 54 | (fakeComponent3 as any).componentDidMount() 55 | assert.deepStrictEqual(fakeComponent3.state, { 0: 4, 1: 1 }) -------------------------------------------------------------------------------- /src/hooks/ref.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { useCounter } from "../context"; 3 | import { HooksComponent } from '../hooks-component'; 4 | import { inputsChange } from '../inputs'; 5 | 6 | export interface HooksComponentRefs { 7 | [counter: number]: React.RefObject 8 | } 9 | 10 | export function useRef(initialValue: T): React.RefObject { 11 | const { component, counter } = useCounter() 12 | const componentRefs: { [counter: number]: React.RefObject } = component.__hooks__.refs 13 | 14 | if (!componentRefs.hasOwnProperty(counter)) { 15 | const ref = React.createRef(); 16 | (ref as any).current = initialValue 17 | componentRefs[counter] = ref 18 | } 19 | 20 | return componentRefs[counter] 21 | } 22 | 23 | 24 | export type HooksComponentImperativeMethods 25 | = [T, any[] | undefined] 26 | 27 | export function useImperativeMethods(ref: React.RefObject, createInstance: () => T, inputs?: any[]): void { 28 | const component = ref.current 29 | if (component) { 30 | const componentImperativeMethods = component.__hooks__.imperativeMethods 31 | 32 | if (!componentImperativeMethods) { 33 | const instance = createInstance() 34 | Object.getOwnPropertyNames(instance).forEach((name) => { 35 | (component as any)[name] = (instance as any)[name] 36 | }) 37 | component.__hooks__.imperativeMethods = [instance, inputs] 38 | } else { 39 | const [oldInstance, oldInputs] = componentImperativeMethods 40 | if (inputsChange(oldInputs, inputs)) { 41 | const instance = createInstance() 42 | Object.getOwnPropertyNames(oldInstance).forEach((name) => { 43 | delete (component as any)[name] 44 | }) 45 | Object.getOwnPropertyNames(instance).forEach((name) => { 46 | (component as any)[name] = (instance as any)[name] 47 | }) 48 | component.__hooks__.imperativeMethods = [instance, inputs] 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/hooks/effect.ts: -------------------------------------------------------------------------------- 1 | import { useCounter } from "../context" 2 | import { HooksInputs, inputsChange } from "../inputs"; 3 | 4 | type EffectCleanup = () => void 5 | type EffectFunc = () => (void | EffectCleanup) 6 | 7 | export interface HooksComponentEffects { 8 | [counter: number]: [EffectFunc | undefined, EffectCleanup | undefined, HooksInputs | undefined] 9 | } 10 | 11 | function useEffectHandler(effects: HooksComponentEffects, counter: number, effectFunc: EffectFunc, inputs?: HooksInputs) { 12 | if (!effects.hasOwnProperty(counter)) { 13 | effects[counter] = [effectFunc, undefined, inputs] 14 | } else { 15 | const [, , oldInputs] = effects[counter] 16 | if (inputsChange(inputs, oldInputs)) { 17 | effects[counter][0] = effectFunc 18 | effects[counter][2] = inputs 19 | } 20 | } 21 | } 22 | 23 | export function runEffects(effects: HooksComponentEffects) { 24 | Object.getOwnPropertyNames(effects).forEach((_counter) => { 25 | const counter = parseInt(_counter) 26 | const [effectFunc, cleanup,] = effects[counter] 27 | if (typeof effectFunc === 'function') { 28 | if (typeof cleanup === 'function') { 29 | cleanup() 30 | } 31 | const nextCleanup = effectFunc() 32 | effects[counter][0] = undefined 33 | effects[counter][1] = typeof nextCleanup === 'function' ? nextCleanup : undefined 34 | } 35 | }) 36 | } 37 | 38 | export function cleanupEffects(effects: HooksComponentEffects) { 39 | Object.getOwnPropertyNames(effects).forEach((_counter) => { 40 | const counter = parseInt(_counter) 41 | const [, cleanup,] = effects[counter] 42 | if (typeof cleanup === 'function') { 43 | cleanup() 44 | } 45 | delete effects[counter] 46 | }) 47 | } 48 | 49 | export function useEffect(effectFunc: EffectFunc, inputs?: HooksInputs) { 50 | const { component, counter } = useCounter() 51 | useEffectHandler(component.__hooks__.effects, counter, effectFunc, inputs) 52 | } 53 | 54 | export function useLayoutEffect(effectFunc: EffectFunc, inputs?: HooksInputs) { 55 | const { component, counter } = useCounter() 56 | useEffectHandler(component.__hooks__.layoutEffects, counter, effectFunc, inputs) 57 | } 58 | -------------------------------------------------------------------------------- /test/fake-hooks-component.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react' 3 | import { HooksComponentState } from '../src/hooks/state' 4 | import { bindContexts } from '../src/hooks/context' 5 | import { runEffects, cleanupEffects } from '../src/hooks/effect' 6 | import { RenderFunc, HooksComponent, bindComponent } from '../src/hooks-component'; 7 | 8 | export function fakeWithHooks(renderFunc: RenderFunc): React.ComponentClass { 9 | const HooksComponentClass = class { 10 | public state: HooksComponent['state'] = {} 11 | public __hooks__: HooksComponent['__hooks__'] = { 12 | ref: React.createRef(), 13 | setters: {}, 14 | dispatchers: {}, 15 | effects: {}, 16 | layoutEffects: {}, 17 | refs: {}, 18 | contexts: {}, 19 | memos: {}, 20 | imperativeMethods: undefined 21 | } 22 | 23 | public render: () => React.ReactNode 24 | public props: Props 25 | 26 | constructor(props: Props) { 27 | this.props = props; 28 | (this.__hooks__.ref as any).current = this 29 | this.render = bindContexts( 30 | this.__hooks__.contexts, 31 | bindComponent(this.__hooks__.ref, renderFunc) 32 | ) 33 | } 34 | 35 | setState(state: Partial, callback?: () => void) { 36 | this.state = { ...this.state, ...state } 37 | callback && callback() 38 | } 39 | 40 | componentWillMount() { 41 | runEffects(this.__hooks__.layoutEffects) 42 | } 43 | 44 | componentDidMount() { 45 | runEffects(this.__hooks__.effects) 46 | } 47 | 48 | componentWillUpdate() { 49 | runEffects(this.__hooks__.layoutEffects) 50 | } 51 | 52 | componentDidUpdate() { 53 | runEffects(this.__hooks__.effects) 54 | } 55 | 56 | componentWillUnmount() { 57 | cleanupEffects(this.__hooks__.effects) 58 | cleanupEffects(this.__hooks__.layoutEffects) 59 | } 60 | 61 | } as any as React.ComponentClass 62 | 63 | HooksComponentClass.displayName = (renderFunc as any).name 64 | return HooksComponentClass 65 | } 66 | -------------------------------------------------------------------------------- /src/hooks/state.ts: -------------------------------------------------------------------------------- 1 | import { useCounter } from "../context"; 2 | 3 | export interface HooksComponentState { 4 | [counter: number]: any 5 | } 6 | 7 | export interface HooksComponentStateSetters { 8 | [counter: number]: SetState 9 | } 10 | 11 | export interface HooksComponentStateDispatchers { 12 | [counter: number]: Dispatch 13 | } 14 | 15 | type State = T | (() => T) 16 | type SetState = (state: T | ((oldState: T) => T), callback?: () => void) => void 17 | 18 | export function useState(defaultState: State): [T, SetState] { 19 | const { component, counter } = useCounter() 20 | const componentState: { [counter: number]: T } = component.state 21 | const componentSetters = component.__hooks__.setters 22 | 23 | if (!componentState.hasOwnProperty(counter)) { 24 | if (typeof defaultState === 'function') { 25 | componentState[counter] = (defaultState as () => T)() 26 | } else { 27 | componentState[counter] = defaultState 28 | } 29 | 30 | componentSetters[counter] = (state, callback?) => { 31 | const componentState: { [counter: number]: T } = component.state 32 | if (typeof state === 'function') { 33 | const oldState = componentState[counter] as T 34 | component.setState({ [counter]: (state as (oldState: T) => T)(oldState) }, callback) 35 | } else { 36 | component.setState({ [counter]: state }, callback) 37 | } 38 | } 39 | } 40 | 41 | return [componentState[counter], componentSetters[counter]] 42 | } 43 | 44 | interface Action { 45 | type: string, 46 | payload?: any 47 | } 48 | 49 | type Reducer = (state: T, action: Action) => T 50 | 51 | type Dispatch = (action: Action, callback?: () => void) => void 52 | 53 | export function useReducer(reducer: Reducer, initialState: T, initialAction?: Action): [T, Dispatch] { 54 | const { component, counter } = useCounter() 55 | const componentState: { [counter: number]: T } = component.state 56 | const componentDispatchers = component.__hooks__.dispatchers 57 | 58 | if (!componentState.hasOwnProperty(counter)) { 59 | if (initialAction) { 60 | componentState[counter] = reducer(initialState, initialAction) 61 | } else { 62 | componentState[counter] = initialState 63 | } 64 | 65 | componentDispatchers[counter] = (action, callback?) => { 66 | const componentState: { [counter: number]: T } = component.state 67 | component.setState({ [counter]: reducer(componentState[counter], action) }, callback) 68 | } 69 | 70 | } 71 | 72 | return [componentState[counter], componentDispatchers[counter]] 73 | } 74 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React Hooks Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 82 | 83 | -------------------------------------------------------------------------------- /src/hooks-component.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { HooksComponentState, HooksComponentStateSetters, HooksComponentStateDispatchers } from './hooks/state' 3 | import { HooksComponentMemos } from './hooks/memo' 4 | import { HooksComponentRefs, HooksComponentImperativeMethods } from './hooks/ref' 5 | import { HooksComponentContexts, bindContexts } from './hooks/context' 6 | import { HooksComponentEffects, runEffects, cleanupEffects } from './hooks/effect' 7 | import { withContext } from './context'; 8 | 9 | export declare class HooksComponent extends React.Component { 10 | public state: HooksComponentState 11 | public __hooks__: { 12 | ref: React.RefObject>, 13 | setters: HooksComponentStateSetters, 14 | dispatchers: HooksComponentStateDispatchers, 15 | refs: HooksComponentRefs, 16 | memos: HooksComponentMemos, 17 | contexts: HooksComponentContexts, 18 | effects: HooksComponentEffects, 19 | layoutEffects: HooksComponentEffects, 20 | imperativeMethods?: HooksComponentImperativeMethods<{}>, 21 | } 22 | } 23 | 24 | export type RenderFunc 25 | = (props: Props, ref: React.RefObject>) => React.ReactNode 26 | 27 | 28 | export function bindComponent(ref: React.RefObject>, renderFunc: RenderFunc) { 29 | const component = ref.current as HooksComponent 30 | return withContext(component, () => renderFunc(component.props, ref)) 31 | } 32 | 33 | export function withHooks(renderFunc: RenderFunc): React.ComponentClass { 34 | const HooksComponentClass = class extends React.Component { 35 | public state: HooksComponent['state'] = {} 36 | public __hooks__: HooksComponent['__hooks__'] = { 37 | ref: React.createRef(), 38 | setters: {}, 39 | dispatchers: {}, 40 | effects: {}, 41 | layoutEffects: {}, 42 | refs: {}, 43 | contexts: {}, 44 | memos: {}, 45 | imperativeMethods: undefined, 46 | } 47 | 48 | constructor(props: Props) { 49 | super(props); 50 | (this.__hooks__.ref as any).current = this 51 | this.render = bindContexts( 52 | this.__hooks__.contexts, 53 | bindComponent(this.__hooks__.ref, renderFunc) 54 | ) 55 | } 56 | 57 | componentWillMount() { 58 | runEffects(this.__hooks__.layoutEffects) 59 | } 60 | 61 | componentDidMount() { 62 | runEffects(this.__hooks__.effects) 63 | } 64 | 65 | componentWillUpdate() { 66 | runEffects(this.__hooks__.layoutEffects) 67 | } 68 | 69 | componentDidUpdate() { 70 | runEffects(this.__hooks__.effects) 71 | } 72 | 73 | componentWillUnmount() { 74 | cleanupEffects(this.__hooks__.effects) 75 | cleanupEffects(this.__hooks__.layoutEffects) 76 | } 77 | 78 | } as React.ComponentClass 79 | 80 | HooksComponentClass.displayName = (renderFunc as any).name 81 | return HooksComponentClass 82 | } 83 | -------------------------------------------------------------------------------- /dist/react-hooks.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],e):e(t.ReactHooks={},t.React)}(this,function(t,u){"use strict";var r=function(t,e){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var o in e)e.hasOwnProperty(o)&&(t[o]=e[o])})(t,e)};var f=[];function s(){var t=f[f.length-1],e=t.counter++;return{component:t.component,counter:e}}function i(t,e){if(t&&e){if(0