react使用hooks案例

2020/7/21 react

# asyncDelay

// 测试延迟
const asyncDelay = ms => new Promise(r => setTimeout(r, ms));
1
2

# sleep

function sleep(sleepTime){
    var start=new Date().getTime();
    while(true){
        if(new Date().getTime()-start>sleepTime){
            break;
        }
    }
}
1
2
3
4
5
6
7
8

# Portal

import React from "react";
import { createPortal } from "react-dom";

class Dialog extends React.Component {
  constructor() {
    super(...arguments);

    const doc = window.document;
    this.node = doc.createElement("div");
    doc.body.appendChild(this.node);
  }

  render() {
    return createPortal(
      <div class="dialog">{this.props.children}</div>, //塞进传送门的JSX
      this.node //传送门的另一端DOM node
    );
  }

  componentWillUnmount() {
    window.document.body.removeChild(this.node);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# useModal

  • create Modal.js
// Modal.js
import React from 'react'
import ReactDOM from 'react-dom'

const Modal = React.memo(({ children, closeModal }) => {
  const domEl = document.getElementById('modal-root')

  if (!domEl) return null
  return ReactDOM.createPortal(
    <div>
      <button onClick={closeModal}>Close</button>
      {children}
    </div>,
    domEl
  )
})

export default Modal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • create useModal.js
// useModal.js
import React, { useState } from "react";

import Modal from "./Modal";

// Modal组件最基础的两个事件,show/hide
export const useModal = () => {
  const [isVisible, setIsVisible] = useState(false);

  const show = () => setIsVisible(true);
  const hide = () => setIsVisible(false);

  const RenderModal = ({ children }: { children: React.ReactChild }) => (
    <React.Fragment>
      {isVisible && <Modal closeModal={hide}>{children}</Modal>}
    </React.Fragment>
  );

  return {
    show,
    hide,
    RenderModal
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  • use modal
import React from "react";

import { useModal } from "./useModal";

const App = React.memo(() => {
  const { show, hide, RenderModal } = useModal();
  return (
    <div>
      <div>
        <p>some content...</p>
        <button onClick={show}>打开</button>
        <button onClick={hide}>关闭</button>
        <RenderModal>
          <p>这里面的内容将会被渲染到'modal-root'容器里.</p>
        </RenderModal>
      </div>
      <div id="modal-root" />
    </div>
  );
});

export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# hooks-reducer

  • create store/context.js
import { createContext } from "react";

const Context = createContext();

export default Context;
1
2
3
4
5
  • create store/redux.js
import React, { useReducer } from "react";
import ContextContainer from "./context";

const defaultState = {
  count: 0
};

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { ...state, count: state.count + 1 };
    case "decrement":
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
}

const ContextProvider = props => {
  const [state, dispatch] = useReducer(reducer, defaultState);
  return (
    <ContextContainer.Provider value={{ state, dispatch }}>
      {props.children}
    </ContextContainer.Provider>
  );
};

export { reducer, ContextProvider };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  • use index.js
import React from "react";
import ReactDOM from "react-dom";
import { ContextProvider } from "./store/redux";
import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <ContextProvider>
      <App />
    </ContextProvider>
  </React.StrictMode>,
  rootElement
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • use App.js
import React, { useContext } from "react";
import "./styles.css";
import CounterContext from "./store/context";
export default function App() {
  const { state, dispatch } = useContext(CounterContext);
  return (
    <div className="App">
      <h1>React Hooks</h1>
      <h2>useContext and useReducer</h2>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+1</button>
      <span> - </span>
      <button onClick={() => dispatch({ type: "decrement" })}>-1</button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# hooks-redux

  • create store/index.js
import { combineReducer } from "./redux";
import User from "./userModule";
import Counter from "./counterModule";

const state = {
  user: User.state,
  counter: Counter.state
};

const reducers = combineReducer({
  user: User.reducer,
  counter: Counter.reducer
});

export default {
  state,
  reducers
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • create store/redux.js
import React, { useContext, useReducer } from "react";

const EMPTY = Symbol("store context");
const StoreContext = React.createContext(EMPTY);

export const Provider = ({ children, store }) => {
  // console.log(store)
  const [state, dispatch] = useReducer(store.reducers, store.state);
  return (
    <StoreContext.Provider value={{ state, dispatch }}>
      {children}
    </StoreContext.Provider>
  );
};

export const useRedux = () => {
  const store = useContext(StoreContext);
  if (store === EMPTY) {
    throw new Error("App Component must be wrapped with <Provider>");
  } else {
    const { state, dispatch } = store;
    return { state, dispatch };
  }
};

export const combineReducer = reducers => {
  return (state = {}, action) => {
    let result = Object.keys(reducers).reduce((newState, key) => {
      newState[key] = reducers[key](state[key], action);
      return newState;
    }, {});
    return result;
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  • create store/counterModule.js
const CounterModule = {
  state: {
    count: 0
  },
  reducer: (state, action) => {
    switch (action.type) {
      case "increment":
        return { ...state, count: state.count + 1 };
      case "decrement":
        return { ...state, count: state.count - 1 };
      default:
        return state;
    }
  }
};

export default CounterModule;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • create store/UserModule.js
const UserModule = {
  state: {
    login: "未登录"
  },
  reducer: (state, action) => {
    switch (action.type) {
      case "SET_TOKEN":
        return { ...state, login: "已登录" };
      case "RESET_TOKEN":
        return { ...state, login: "未登录" };
      default:
        return state;
    }
  }
};

export default UserModule;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • use index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "./store/redux";
import store from "./store/index";
import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  rootElement
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • use App.js
import React from "react";
import "./styles.css";
import { useRedux } from "./store/redux";

export default function App() {
  const { state, dispatch } = useRedux();

  return (
    <div className="App">
      <h1>React Hooks</h1>
      <h2>useContext and useReducer</h2>
      <p>Count:{state.counter.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+1</button>
      <span> - </span>
      <button onClick={() => dispatch({ type: "decrement" })}>-1</button>
      <hr />
      <p>login:{state.user.login}</p>
      <button onClick={() => dispatch({ type: "SET_TOKEN" })}>登录</button>
      <span> - </span>
      <button onClick={() => dispatch({ type: "RESET_TOKEN" })}>退出</button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# hooks-unstated-next

  • create store/index.js
import { useState } from "react";
import { createContainer } from "./redux";

const defaultState = {
  count: 0
};

function Store(initialState = defaultState) {
  let [state, setState] = useState(initialState);
  let increment = () => setState(pre => ({ count: pre.count + 1 }));
  let decrement = () => setState(pre => ({ count: pre.count - 1 }));
  return {
    state,
    increment,
    decrement
  };
}

export default createContainer(Store);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • create store/redux.js
// https://github.com/jamiebuilds/unstated-next
import React from "react";

const EMPTY = Symbol("unstated-next");

export function createContainer(useHook) {
  let Context = React.createContext(EMPTY);

  function Provider(props) {
    let value = useHook(props.initialState);
    return <Context.Provider value={value}>{props.children}</Context.Provider>;
  }

  // eslint-disable-next-line no-shadow
  function useContainer() {
    let value = React.useContext(Context);
    if (value === EMPTY) {
      throw new Error("Component must be wrapped with <Container.Provider>");
    }
    return value;
  }

  return { Provider, useContainer };
}

export function useContainer(container) {
  return container.useContainer();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  • use index.js
import React from "react";
import ReactDOM from "react-dom";
import Counter from "./store/index";
import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <Counter.Provider>
      <App />
    </Counter.Provider>
  </React.StrictMode>,
  rootElement
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • use App.js
import React from "react";
import "./styles.css";
import Counter from "./store/index";

export default function App() {
  const store = Counter.useContainer();
  return (
    <div className="App">
      <h1>React Hooks</h1>
      <h2>use unstated-next</h2>
      <p>Count: {store.state.count}</p>
      <div>
        <button onClick={store.increment}>+1</button> ~{" "}
        <button onClick={store.decrement}>-1</button>
      </div>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18