hook!

在react conf 2018上,react发布了一个新的提案hook。稳定的正式版可能要等一两个月以后才能出来,目前能够在v16.7.0-alpha上试用到rfc上各类提问。css

那么这个hook究竟是个什么呢,官方的定义是这样的html

Hooks are a new feature proposal that lets you use state and other React features without writing a class.react

这是一个比class更直观的新写法,在这个写法中react组件都是纯函数,没有生命周期函数,但能够像class同样拥有state,能够由effect触发生命周期更新,提供一种新的思路来写react。(虽然官方再三声明咱们绝对没有要拿掉class的意思,但hook将来的目标是覆盖全部class的应用场景)git

其实在看demo演示的时候我是十分抗拒的,没有生命周期函数的react是个什么黑魔法,虽然代码变得干净了很多,但写法实在是发生了很大的转变,有种脱离掌控的不安全感,我甚至有点怀疑我能不能好好debug。github

演示的最后dan的结束语是这样的redux

hook表明了咱们对react将来的愿景,也是咱们用来推进react前进的方法。所以咱们不会作大幅的重写,咱们会让旧的class模式和新的hook模式共存,因此咱们能够一块儿慢慢的接纳这个新的react。api

我接触react已经四年了,第一次接触它的时候,我第一个想问的是,为何要用jsx。第二个想问的是,为何要用这个logo,毕竟咱们又不是叫atom,也不是什么物理引擎。如今我想到了了一个解释,原子的类型和属性决定了事物的外观和表现,react也是同样的,你能够把界面划分为一个个独立的组件,这些组件(component)的类型(type)和属性(props)决定了最终界面的外观和表现。讽刺的是,原子一直被认为是不可分的,因此当科学家第一次发现原子的时候认为这就是最小的单元,直到后来在原子中发现了电子,实际上电子的运动更能决定原子能作什么。hook也是同样的,我不认为hook是一个新的react特性,相反的,我认为hook能让我更直观的了解react的基本特性像是state、context、生命周期。hook能更直观的表明react,它解释了组件内部是如何工做的,我认为它被遗落了四年,当你看到react的logo,能够看到电子一直环绕在那里,hook也是,它一直在这里。数组

因而我决定干了这杯安利。安全

试了几个比较基本的api写了几个demo,代码在 github.com/lllbahol/re…, 彻底的api还请参考官方文档 reactjs.org/docs/hooks-…bash

api

基本的hook有三个

  • useState(至关于state)
  • useEffect(至关于componentDidUpdate, componentDidMount, componentWillUnmount)
  • useContext(至关于Context api)

useState

const [state, setState] = useState(initialState);

import { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
复制代码

在这里react组件就是一个简单的function

  • useState(initialState)也是一个函数,定义了一个state,初始值为initialState,返回值是一个数组,0为state的值,1为setState的方法。

  • 当state发生变化时,函数组件刷新。

  • 能够useState屡次来定义多个state,react会根据调用顺序来判断。

你必定也写过一个庞大的class, 有一堆handler函数,由于要setState因此不能挪到组件外面去,而后render函数就被挤出了页面,每次想看render都要把页面滚到底下。

如今由于useState是函数,因此它能够被挪到组件外面,连带handler一块儿,下面是一个具体一点的表单例子。

import React, { useState } from 'react';

// 表单组件,有name, phone两个输入框。
export default () => {
  const name = useSetValue('hello');
  const phone = useSetValue('120');
  return (
    <React.Fragment>
      <Item {...name} />
      <br />
      <Item {...phone} />
    </React.Fragment>
  );
}

// controlled input component
const Item = ({ value, setValue }) => (
  <React.Fragment>
    <label>{value}</label>
    <br />
    <input value={value} onChange={setValue} />
  </React.Fragment>
);

// 能够将state连同handler function一块儿挪到组件外面。
// 甚至能够export出去,让其余组件也能使用这个state逻辑
const useSetValue = (initvalue) => {
  const [value, setValue] = useState(initvalue);
  const handleChange = (e) => {
    setValue(e.target.value);
  }
  return {
    value,
    setValue: handleChange,
  };
}

复制代码

useEffect

这个api可让你在函数组件中使用反作用(use side effects),常见的会产生反作用的方式有获取数据,更新dom,绑定事件监听等,render只负责渲染,通常会等到dom加载好以后再去调用这些反作用方法。

useEffect(didUpdate/didMount);

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);
复制代码

useEffect能够接受两个参数

  • 第一个参数为一个effect函数,effect函数在每次组件render以后被调用,至关于componentDidUpdate和componentDidMount两个生命周期之和。effect函数能够返回一个clear effect函数,会在下一次的effect函数执行以前执行,原来componentWillUnmount里执行的东西均可以交给它。调用顺序是:render(dom加载完成) => prevClearUseEffect => useEffect

  • 第二个参数是一个数组,只有当数组传入的值发生变化时,effect才会执行。

上面的写法若是用class实现的话应该是下面这样的。咱们按时间前后将一个会产生反作用的函数的第1次调用、第2-n次调用、卸载分红3截,实际上它们老是一一对应出现的,应该是一个总体。

componentDidMount() {
  this.subscription = props.source.subscribe();
}

componentDidUpdate() {
  this.subscription = props.source.subscribe();
}

componentWillUnmount () {
  subscription.unsubscribe();
}
复制代码

具体案例能够看一个轮播组件的demo

import React, { useState, useEffect } from 'react';
import './index.css';

const IMG_NUM = 3;

export default () => {
  const [index, setIndex] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);
  useEffect(() => {
    // 每次组件刷新时触发effect, 至关cDM cDU
    if (isPlaying) {
      const timeout = setTimeout(() => {
        // 改变state, 刷新组件
        handleNext();
      }, 2000);
      // 返回清除effect的回调函数, 在每次effect调用完以后,若是有则执行
      return () => clearTimeout(timeout);
    }
    // 若是不想每次render以后都调一次effect, 可使用第二个参数做为筛选条件
  }, [index, isPlaying]);

  const handleNext = () => {
    setIndex((index + 1) % IMG_NUM);
  }
  const handlePrev = () => {
    setIndex((index - 1 + IMG_NUM) % IMG_NUM);
  }
  const handlePause = () => {
    setIsPlaying(!isPlaying);
  };
  return (
    <div>
      <div className="img">{index}</div>
      <button onClick={handlePrev}>prev</button>
      <button onClick={handlePause}>pause</button>
      <button onClick={handleNext}>next</button>
    </div>
  )
}
复制代码

useContext

const context = useContext(Context);

若是对react比较熟悉的话,应该用过Context这个api,用于在组件之间传递数据。useContext接受一个context对象(React.createContext生成),返回context.Consumer中得到的值。

export const Context = React.createContext(null);

function Parent() {
  const someValue = 'haha';
  return (
    <Context.Provider value={someValue}>
      <DeepTree>
       	<DeepChild />
      </DeepTree>
    </Context.Provider>
  );
}
复制代码
function DeepChild() {
  const someValue = useContext(Context);
  return (<div>{someValue}</div>)
}

复制代码

16.7以前的Consumer写法是render props

function DeepChild() {
  return (
    <Context.Consumer>
      {
        (someValue) => <div>{someValue}</div>
      }
    </Context.Consumer>
  )
}
复制代码

彷佛还能忍受,可是可是,为了不没必要要的刷新通常推荐用多个Context来传递刷新周期不一样的数据,所以按原来的render-props写法很容易陷入多重嵌套地狱(wrapper-hell),颇有可能你真正的渲染代码在十几个缩进后面才开始出现。继代码上下滚问题以后咱们又出现了代码左右滚问题。

<Consumer1>
  {
    (value1) => (
      <Consumer2>
        {
          (value2) => (
            ...
          )
        }
      </Consumer2>
    )
  }
</Consumer1>

// 我怎么尚未被同事打死🤦

复制代码

useReducer

还有一堆高级hook

其中有一个useReducer

就是你们熟悉的那个redux里的reducer,来段模板代码让你们回忆一下。

const mapStateToProps = createStructuredSelector({
	...
});

const mapDispatchToProps = (dispatch) => ({
  ...
});

const withReducer = injectReducer({ ... });

const withConnect = connect(mapStateToProps, mapDispatchToProps);

export default compose(withReducer, withConnect)(Component);
复制代码

以上的这些,使用了useReducer以后都没有了。

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, {count: initialCount});
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'reset'})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}
复制代码

我还用useReducer实现了一个todo的demo,代码分了好几个文件就不放上来了 github.com/lllbahol/re…

为何要用hook

除了上面提到的,还有官方罗列出来的一些时常会在写class时遇到的麻烦

  • class组件间不能复用与state关联的代码,hook能够作到这一点。
  • 复杂而庞大的class组件很难被理解,hook可以让你把组件拆成更小的独立单元
  • 理解class是一件困难的事,不管是对人仍是对开发工具而言都是这样。好比class里面的this指向的是组件,在箭头函数写法出来以前,咱们不得不手动绑定this到调用函数的对象上。

总的来讲

用react也很久了,工程越写越复杂,组件间的数据传递是一个很大的问题,从传统的传回调函数,到跨多层多组件共享数据的时候使用redux,后来嫌模板代码太多又本身封了一层render-props结果掉进wrapper嵌套地狱的坑里,Context出来的时候开心了一下子而后发现依然在坑里。写是能写的,就是恐惧,每写一层,个人代码就又缩进了三个tab,离被同事打死又前进三步。

useContext,useReducer的用法让我想到了高阶,不一样的是能够直接用变量接住而不是挂在props上,所以不用考虑props名冲突问题,但能达到高阶一层层包裹数据的效果。

从现有的文档来看,新的api很是的多,一些是咱们熟悉的用法一些则是彻底新的东西,且暂时还没能覆盖全部生命周期场景(好比getDeriveStateFromProps),但不着急,能够一步一步来。

hook正式版发布以后我还会来更新一次这个文档,在工程里正式使用一段时间以后会再更新一次,先奶一口。

参考

  1. www.youtube.com/watch?v=dpw… 官方介绍hook的视频
  2. reactjs.org/docs/hooks-… 官方文档
  3. reactjs.org/docs/hooks-… 一些常见问题的官方解答
相关文章
相关标签/搜索