[react] hooks
react Hooks
Hooks是什么?
functional component在使用的时候有一些限制,比如需要生命周期、state的时候就不能用functional component。 而有了Hooks,你就可以在funtional component里,使用class component的功能:props,state,context,refs,和生命周期函数等等。
1.组件之间很难复用逻辑
之前如果需要复用逻辑,常用的两种方式是render props 跟 higher-order components。但是这两种方式都需要重构代码,所以比较麻烦。
最重要的是,用这两种方式的话,在React Devtools里,会看到很多的嵌套组件。
2.复杂组件很难理解
在之前的class component里,生命周期函数里通常放着不相关的代码,而相关的代码确要放在不同的生命周期函数里。
class App extends React.component {
  componentDidMount() {
    window.addEventListener('scroll', () => {console.log('a')})
    this.fetchData();
  }
  componentDidUpdate() {
    this.fetchData();
  }
  componentWillUnmount() {
    window.removeEventListener('scroll', () => {console.log('a')})
  }
  render() {
    return <div>ddd</div>
  }
}
在componentDidMount里做事件绑定,获取数据。在componentWillUnMount里解绑事件。
这样做的问题是:componentDidMount装着的代码都是不相关的,而相关联的事件绑定以及事件解绑,分散在componentDidMount 跟 componentWillUnMount里。这样如果组件的逻辑越写越复杂之后,就会变得很难维护易出bug。
$$ 3.class比较难学
React团队发现class是初学者学习React的大障碍。要学习class component,必须要知道几点:
- this在JS是如何工作的(光是这个就够绕的)
- 记得绑定事件
- 了解state,props,state以及从上而下的数据流
- functional component跟class component的区别,如何使用它们
如何使用
理解了Hooks诞生的原因,接着来看看要如何使用。
假设需要实现一个功能,点击app时候,count数目加一。
Class:
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>
    );
  }
}
Hooks:
import React, { useState } from 'react';
    
    function Example() {
      // Declare a new state variable, which we'll call "count"
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
在这里demo中。useState就是Hook。通过它来在function component里加入state数据。
1. 定义state变量
Class:
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
Hooks:
import React, { useState } from 'react';
function Example() {
  // 通过useState这个Hooks定义state变量:count。并且通过useState给count赋初始值0,只在初始化时候使用一次
  const [count, setCount] = useState(0);
}
在function component里,是没有this的。所以没办法向Class那样用this来创建state,这时候Hooks闪亮登场。
通过useState这个hooks可以定义count这个state变量。 由Hooks定义的state变量不一定要是object,可以是string、number。传入的内容相当于给变量赋初始值。
function ExampleWithManyStates() {
  // Declare multiple state variables!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}
const [fruit, setFruit] = useState('banana');
//相等
  var fruitStateVariable = useState('banana'); // Returns a pair
  var fruit = fruitStateVariable[0]; // First item in a pair
  var setFruit = fruitStateVariable[1]; // Second item in a pair
2. 渲染state
Class:
<p>You clicked {this.state.count} times</p>
Hooks:
<p>You clicked {count} times</p>
可以不需要用this,直接使用count
3.更新state
Class:
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
    Click me
</button>
Hooks:
<button onClick={() => setCount(count + 1)}>
    Click me
</button>
const [count, setCount] = useState(0);
useState返回两个参数,一个是当前state的值,还有一个其实是一个函数,用来改变state的值,就是setCount。
类似setState,但是不同的是,它不会将旧的state跟新的state合并在一起,而是覆盖式的重写state的值。
useEffect
说完了functional component里面如何使用state之后,再来看如何用Effect Hook来取代生命周期。
一般都会在生命周期componentDidMount, componentDidUpdate与 componentWillUnmount中做一些副作用的操作,例如:获取数据,订阅,手动改变DOM。而在hooks里,这些生命周期函数都被统一成一个方法 useEffect。
import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);
  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Effects Hooks 就在functional component里, 所以它可以直接访问props跟state。 React在每次render之后都会调用effect,包括第一次render。
但是这里还遗留两个问题
1
在开篇说到,class component有个问题就是生命周期函数里的代码都是不相关的,而相关的代码确要被打散在不同的生命周期函数里。这个问题用Hooks的话就可以解决。
比如绑定、解绑事件,在使用class的时候,在componentDidMount里监听了一个事件,之后需要在componentWillMount里给它解绑。
用Hook只需要在useEffect一个函数就可以做到。它可以通过返回一个函数来专门做清除的工作,代码如下:
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
   
    // 在这里返回一个函数来做这件事
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
在这个case中,unsubscribeFromFriendStatus不仅仅会在组件unmount的时候 调用,同时在重新渲染的时候也会调用。
如果只想useEffect在mount与unmount时候调用,需要这样传一个[] 作为第二个参数。
useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  }, []);
2
有时候并不想每次state的改变,都去调用useEffect。 在class里,会这样做
componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}
在Hooks,可以通过给useEffect传入第二个参数,即它关注的state变量,来做到这件事。
例如:当count改变时候,才去调用useEffect。
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
注意事项:
- Hooks只可以在顶层使用。也就是说它不能写在循环体,条件渲染,或者嵌套function里
- 只可以在React的function组件里使用Hooks。
class FriendStatusWithCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0, isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }
  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }
  // ...
function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  // ...
}
其他hooks
还有一些不太常用的内置钩子。使用useContext可以订阅React context而不用引入嵌套:
function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}
useReducer则允许使用一个reducer来管理一个复杂组件的局部状态(local state):
function Todos() {
  const [todos, dispatch] = useReducer(todosReducer);
  // ...
}
其实Hooks就是帮助我们在function component里直接使用原来class component才有的特性。
一些建议
- 
是否需要用hoooks重构以前的代码? 
 No,react团队不推荐用hooks重新写一遍。 推荐做法是新的组件可以直接使用,然后需要改老组件代码的时候在顺便改就行了。
- 
支持Hooks的工具 
 React DevTools对hooks已经支持。同时推荐安装hooks的eslint校验库 eslint-plugin-react-hooks 。
- 
Reac团队下一步计划 
 因为现在React Hooks还不能完全cover所有class的功能,虽然他们已经很相近了。目前为止Hooks不支持两个不常使用的API getSnapshotBeforeUpdate 跟 componentDidCatch,但是React团队正在努力,未来会支持的。
坑
在闭包里获取不到最新的state/props,需要使用useRef实时更新
 
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号