React-hooks

React Hooks: let you use React without classes.(对于已有的使用class定义的React组件,官方不推荐全部重写。可将react hooks用于新创建的React组件)。

使用class定义React component有什么弊端:a. this指向不明确; b. 定义的handle函数需要bind

1. useState:接受的唯一参数为初始化的state值(仅在首轮render中被使用,不一定是object类型)。 返回一对参数:第一个为当前的state,第二个为其更新函数。

function ExampleWithManyStates() {
  // Declare multiple state variables!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

这里,与使用class定义的component在写法上进行对比。

class Example extends React.Component {
    constructor (props) {
        super(props);
        this.setState = {
            age: 42,
            fruit: 'banana',
            todos: [{ text: 'React Hooks' }]
        };
    }
    render () {
        return ....
    }
}

 2. useEffect(一个component中可以有多个useEffect)

何为effect(效应)?affect other components and can’t be done during rendering.如fetch data, set up a subscription, manually changing the DOM.

useEffect起到了与React classes中 componentDidMountcomponentDidUpdate, componentWillUnmount相同的作用,但只不过是统一于一身。相当于通过useEffect传递了一个函数给React, React会在每次render之后(从浏览器渲染的角度讲,是在paint阶段之后)去调用effect函数(包括首轮render)。

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>
  );
}

当组件中含有subscribe时,通常在componentDidMount时添加subscribe,在componentWillUnmount时取消订阅。在这种情况下,需要及时进行clean up,以免造成内存泄漏。

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);
    // Specify how to clean up after this effect, when the component will unmount:
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

class写法如下:

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
// 如果不加
componentDidUpdate生命周期,可能会因为props改变导致内存泄漏。
   componentDidUpdate(prevProps) {
    // Unsubscribe from the previous friend.id
    ChatAPI.unsubscribeFromFriendStatus(
      prevProps.friend.id,
      this.handleStatusChange
    );
    // Subscribe to the next friend.id
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }); } render() { if (this.state.isOnline === null) { return 'Loading...'; } return this.state.isOnline ? 'Online' : 'Offline'; } }

 此外,useEffect还有第二个可选的参数(仅当该参数改变时,才去调用effect函数)

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // Only re-subscribe if props.friend.id changes

当第二个参数为[]时,effect函数的调用不依赖于组建的任何props或state,即effect函数不会re-run.

3. 使用hooks的规则

  • 只在顶层调用React hooks,不能用于循环、条件、内嵌函数中,保证每轮render中React hooks的调用顺序不变。(如果想要使用条件判断,要将其放在useEffect内部)
  • 只在React function函数及自定义的hooks中调用hook函数

 React提供的eslint-plugin-react-hooks默认包含在create-react-app中。

4. 使用自定义hooks,特点:1. 以use开头 2. 在其中调用其他hooks

作用: 重用状态逻辑(stateful logic)

import React, { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);
    // 传入props后,isOnline被设为true.
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });
    // 返回
  return isOnline;
}

在两个组件中重用。

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

 注意:自定义的hook仅仅用作状态逻辑的重用,并没有共享state。在重用过程中,每次调用都是独立的。

官网给出的另一个使用自定义hook的例子。其中,当recipientID改变时,自定义的useFriendStatus会解除对原来recipientID的订阅,并订阅新的recipientID。

const friendList = [
  { id: 1, name: 'Phoebe' },
  { id: 2, name: 'Rachel' },
  { id: 3, name: 'Ross' },
];

function ChatRecipientPicker() {
  const [recipientID, setRecipientID] = useState(1);
  const isRecipientOnline = useFriendStatus(recipientID);

  return (
    <>
      <Circle color={isRecipientOnline ? 'green' : 'red'} />
      <select
        value={recipientID}
        onChange={e => setRecipientID(Number(e.target.value))}
      >
        {friendList.map(friend => (
          <option key={friend.id} value={friend.id}>
            {friend.name}
          </option>
        ))}
      </select>
    </>
  );
}

5. 其他内置hooks API

5.1 useContext: 参数必须是一个context对象(不能是cntext.provider或context.consumer),返回组件树中距离调用该API组件最近的Context.Provider的context value.当context value改变时,调用该API的组件将重新渲染。

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

5.2 useReducer:是useState的替代,用于处理包含多个sub-value的复杂state逻辑,或者下一个state依赖于前一个。

const [state, dispatch] = useReducer(reducer, initialArg, init);

重写useState的count组件。这里,可以将初始化的state传入useReducer的第二个参数。

const initialState = {count: 0};

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

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

useReducer API还允许我们传入第三个参数——初始化函数。通过调用此函数init(initialArg)返回初始化的state.

function init(initialCount) {
  return {count: initialCount};
}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>

        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

当返回的state与原来的state相等(Object.is)时,该组件的子组件不会重渲染,也不会触发effect.

 5.3 useCallback: 当依赖[a,b]中的一个发生改变时,才会返回一个memorized callback。

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useCallback(fn, dep) 等价于 useMemo(() => fn, dep).

5.4 useMemo:返回一个memorized value。且只有当依赖[a,b]中的一个发生改变时,才会重新计算memorized value。(可用于性能优化,不必要的不会执行)

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

传递给useMemo的函数会在render过程中调用,这一点区别于useEffect中的函数。

如果没有第二个数组参数,每次渲染都会计算一个新的value.

5.5 useRef: 返回一个可变的ref对象,其current值由传入的参数initialValue进行初始化。

const refContainer = useRef(initialValue);
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

返回的ref对象的current属性发生改变时并不会触发re-render.

5.6 useLayoutEffect: 与useEffect基本相似,只是用在browser paint阶段之前。一般更推荐使用useEffect.

 

posted @ 2020-03-07 12:17  cecelia  阅读(450)  评论(0编辑  收藏  举报