美文网首页
ReactHook快速上车

ReactHook快速上车

作者: Jafeney | 来源:发表于2020-09-09 16:55 被阅读0次

    React16.8开始内置了10个Hook,核心是2个:

    • 状态管理:useState
    • 副作用管理:useEffect

    有状态的函数

    useState

    有状态组件写法:

    class Example extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            count: 0
          };
        }
        render() {
          return (
            <div>
              <p>You clicked {this.state.count} times</p>
              <button onClick={() => this.setState({ count: this.state.count + 1 })}>
                Click me
              </button>
            </div>
          );
        }
    }
    

    无状态组件写法:

    const Example = props => {
      const { count, onClick } = props;
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={onClick}>
            Click me
          </button>
        </div>
      )
    }
    

    hooks是有状态的函数:

    import { useState } from 'react';
    const Example = () => {
      const [count, setCount] = useState(0);
      return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
              Click me
            </button>
        </div>
      )
    }
    

    注意,useState生成的setter在更新state的时候不会合并:

    const [data, setData] = useState({ count: 0, name: 'abc' });   // name没有被使用,应该分开声明
    useEffect(() => {
      // data: { count: 0, name: 'abc' } -> { count: 0 }
      setData({ count: 1 })
    })
    

    在我们的纯函数组件里,每个useState都会生产一对state和stateSetter,我们无需考虑更多的状态树的设计和组件的划分设计,逻辑代码可以直接从根组件写起。

    我们应用的发展途径大致分为以下3个阶段,基于hook开发过程将更有弹性:

    • 前期farm:只需要把相关 state 组合到几个独立的 state 变量即可应付绝大多数情况
    • 中期gank:当组件的状态逐渐变得多起来时,我们可以很轻松地将状态的更新交给reducer来管理
    • 后期团战:不光状态多了,状态的逻辑也越来越复杂的时候,我们可以几乎0成本的将繁杂的状态逻辑代码抽离成自定义hook解决问题

    高度灵活的redux,纯粹无依赖

    不同于真正的redux,在实际应用中,hook带来了一种更加灵活和纯粹的模式。现在我们可以用10行代码实现一个全局的redux,也可以用2行代码随时随地实现一个局部的redux:

    10行代码实现一个全局的redux:

    import React from 'react';
    const store = React.createContext(null);
    export const initialState = { name: 'aa' };
    export function reducer(state, action) {
      switch (action.type) {
        case 'changeName': return { ...state, name: action.payload };
        default: throw new Error('Unexpected action');
      }
    }
    export default store;
    

    Provider根组件挂上:

    import React, { useReducer } from 'react';
    import store, { reducer, initialState } from './store';
    
    function App() {
        const [state, dispatch] = useReducer(reducer, initialState);
        return (
            <store.Provider value={{ state, dispatch }}>
                <div />
            </store>
        )
    }
    

    子组件调用:

    import React, { useContext } from 'react';
    import store from './store';
    
    function Child() {
        const { state, dispatch } = useContext(store);
    }
    

    随时随地的一个局部redux:

    import React, { useReducer } from 'react';
    const initialState = { name: 'aa' };
    function reducer(state, action) {
        switch (action.type) {
          case 'changeName': return { ...state, name: action.payload };
          default: throw new Error('Unexpected action');
        }
    }
    
    function Component() {
        const [state, dispatch] = useReducer(reducer, initialState);
        ...
    }
    

    自定义hook

    当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。而组件和 hook 都是函数,所以也同样适用这种方式。不同的是,hook 是有状态的函数,它能实现以往纯函数所不能做到的更高级别的复用——状态逻辑的复用。

    component写法:

    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          count: 0,
          name: undefined
        };
      }
      componentDidMount() {
        service.getInitialCount().then(data => {
          this.setState({ count: data });
        });
        service.getInitialName().then(data => {
          this.setState({ name: data });
        });
      }
      componentWillUnmount() {
        service.finishCounting().then(() => {
          alert('计数完成');
        });
      }
      addCount = () => {
        this.setState({ count: this.state.count + 1 });
      };
      handleNameChange = name => {
        this.setState({ name });
      };
      render() {
        const { count, name } = this.state;
        return (
          <div>
            <div>
              <p>You clicked {count} times</p>
              <button onClick={this.addCount}>Click me</button>
            </div>
            <Input value={name} onChange={this.handleNameChange} />
          </div>
        );
      }
    }
    

    hook写法:

    function useCount(initialValue) {
      const [count, setCount] = useState(initialValue);
      useEffect(() => {
        service.getInitialCount().then(data => {
          setCount(data);
        });
        return () => {
          service.finishCounting().then(() => {
            alert('计数完成');
          });
        };
      }, []);
      function addCount() {
        setCount(c => c + 1);
      }
      return { count, addCount };
    }
    function useName(initialValue) {
      const [name, setName] = useState(initialValue);
      useEffect(() => {
        service.getInitialName().then(data => {
          setName(data);
        });
      }, []);
      function handleNameChange(value) {
        setName(value);
      }
      return { name, handleNameChange };
    }
    const App = () => {
      const { count, addCount } = useCount(0);
      const { name, setName } = useName();
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={addCount}>Click me</button>
          <Input value={name} onChange={setName} />
        </div>
      );
    };
    

    如上,使用component的写法里,count和name,还有与之相关的一票儿逻辑,散落在组件的生命周期和方法里。虽然我们可以将组件的state和变更action抽成公共的,但涉及到副作用的action,到最终还是绕不开组件的生命周期。但一个组件的生命周期只有一套,不可避免的会出现一些完全不相干的逻辑写在一起。如此一来,便无法实现完全的状态逻辑复用。

    我们再看看使用hook的写法,我们将count相关的逻辑和name相关的逻辑通过自定义hook,封装在独立且封闭的逻辑单元里。以往class组件的生命周期在这里不复存在。生命周期是和UI强耦合的一个概念,虽然易于理解,但它天然距离数据很遥远。而hook以一种类似rxjs模式的数据流订阅实现了组件的副作用封装,这样的好处就是我们只需要关心数据。所以hook所带来的,绝不仅仅只是简化了state的定义与包装。

    自定义hook实现了状态逻辑与UI分离,通过合理抽象自定义hook,能够实现非常高级别的业务逻辑抽象复用

    hook原理

    let hooks, i;
    function useState() {
      i++;
      if (hooks[i]) {
        // 再次渲染时
        return hooks[i];
      }
      // 第一次渲染
      hooks.push(...);
    }
    // 准备渲染
    i = -1;
    hooks = fiber.hooks || [];
    // 调用组件
    Component();
    // 缓存 Hooks 的状态
    fiber.hooks = hooks;
    

    本文由博客一文多发平台 OpenWrite 发布!

    相关文章

      网友评论

          本文标题:ReactHook快速上车

          本文链接:https://www.haomeiwen.com/subject/nfzeektx.html