hanyuxinting / Blog

记录点滴
1 stars 0 forks source link

React 学习- react hooks #21

Open hanyuxinting opened 5 years ago

hanyuxinting commented 5 years ago

React Hooks

React 16.8、useState、

React 16.8.0 is the first release to support Hooks. When upgrading, don’t forget to update all packages, including React DOM. React Native will support Hooks in the next stable release.

why ?

how to do?

Hooks 概览

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

State Hook

初识

//  We import the useState Hook from React. It lets us keep local state in a function component.
import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);
/* // 数组解构
 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
*/

  return (
    <div>
      <p>You clicked {count} times</p>
//  When the user clicks, we call setCount with a new value. React will then re-render the Example component, passing the new count value to it.
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useState is a Hook; useState returns a pair: the current state value and a function that lets you update it.

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

进阶

等价的 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 and Function Components
// function components
const Example = (props) => {
  // You can use Hooks here!
  return <div />;
}

// or
function Example(props) {
  // You can use Hooks here!
  return <div />;
}

Hooks don’t work inside classes. But you can use them instead of writing classes.

What’s a Hook?

A Hook is a special function that lets you “hook into” React features. e.g. : useState is a Hook that lets you add React state to function components.

If you write a function component and realize you need to add some state to it, previously you had to convert it to a class. Now you can use a Hook inside the existing function component.

Declaring a State Variable
// in class components
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

// useState in hooks, in a function components
// In a function component, we have no this, so we can’t assign or read this.state. Instead, we call the useState Hook directly inside our component
// The only argument to the useState() Hook is the initial state. 
// If we wanted to store two different values in state, we would call useState() twice.
// useState returns a pair of values: the current state and a function that updates it.

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);
Reading State
// in a class
 <p>You clicked {this.state.count} times</p>

// in a function
<p>You clicked {count} times</p>
Updating State
// in a class
 <button onClick={() => this.setState({ count: this.state.count + 1 })}>
    Click me
  </button>

// in a function
<button onClick={() => setCount(count + 1)}>
    Click me
  </button>

Effect Hook

useEffect,adds the ability to perform side effects from a function component.

Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API. Effects are declared inside the component so they have access to its props and state.

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 may also optionally specify how to “clean up” after them by returning a function.

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

多个effect 时,

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

Effects Without Cleanup

Example Using Classes
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

// we have to duplicate the code between these two lifecycle methods in class.
// 其实这种操作是每次渲染之后都该做的,只是react  class 组件没有这种方法。。。
  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }

// we have to duplicate the code between these two lifecycle methods in class.
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}
Example Using Hooks

Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser from updating the screen.

Effects with Cleanup

For example, we might want to set up a subscription to some external data source. In that case, it is important to clean up so that we don’t introduce a memory leak!

Example Using Classes
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
    );
  }

  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';
  }
}
Example Using Hooks

code for adding and removing a subscription is so tightly related that useEffect is designed to keep it together. If your effect returns a function, React will run it when it is time to clean up.

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

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

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
// This is the optional cleanup mechanism for effects.
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

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

When?? React performs the cleanup when the component unmounts. effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.

Why Effects Run on Each Update

 componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

// status会改变,只有在update中不断侦听status,取消订阅、再次订阅才能满足需求;否则会异常。
  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
    );
  }

也因此,effect需要每次都重置。。 以下,可以省去很多代码,还规避了内存泄漏的问题。

function FriendStatus(props) {
  // ...
  useEffect(() => {
    // ...
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

Optimizing Performance by Skipping Effects

In some cases, cleaning up or applying the effect after every render might create a performance problem.

// in a class
componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

// with hooks
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
// 默认记住上一个 count,如果当前count值与上一个值一样,则不执行effect。

// about cleanup
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

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument.

// 那如何防止频繁run effect 呢?

Hooks 规则

Hooks are JavaScript functions, but they impose two additional rules:

一个插件:linter plugin

ESLint Plugin

npm install eslint-plugin-react-hooks --save-dev

// Your ESLint configuration
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
    "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
  }
}

当声明多个state 或者 effect 时,react 是怎么知道他们对应哪个 useState 呢?

// ------------
// First render
// ------------
useState('Mary')           // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm)     // 2. Add an effect for persisting the form
useState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle)     // 4. Add an effect for updating the title

// -------------
// Second render
// -------------
useState('Mary')           // 1. Read the name state variable (argument is ignored)
useEffect(persistForm)     // 2. Replace the effect for persisting the form
useState('Poppins')        // 3. Read the surname state variable (argument is ignored)
useEffect(updateTitle)     // 4. Replace the effect for updating the title

// ...

bad

  // 🔴 We're breaking the first rule by using a Hook in a condition
  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }

so, 不要放在嵌套函数里,一定要放在top level上~可以用下边的方式判断~~不要破坏顺序。。

useEffect(function persistForm() {
    // 👍 We're not breaking the first rule anymore
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

创建自己的Hooks

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

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

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

  return isOnline;
}
// component 1
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

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

// component 2
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

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

Hooks 约定俗成的一些东西: Custom Hooks are more of a convention than a feature. If a function’s name starts with ”use” and it calls other Hooks, we say it is a custom Hook.

可是如果以前代码中使用use打头,但又没有hooks功能呢??

The useSomething naming convention is how our linter plugin is able to find bugs in the code using Hooks.

Custom Hooks are a convention that naturally follows from the design of Hooks, rather than a React feature.

Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.(不共享数据,之共享处理的逻辑)

Each call to a Hook gets isolated state. 其实我们在实现hook时,实用了useState 或者 useEffect,而这两个的调用都是相互独立的。

其他的Hooks-Hooks API

a few less commonly used built-in Hooks that you might find useful. 如:useContext lets you subscribe to React context without introducing nesting。

function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}

useReducer lets you manage local state of complex components with a reducer:

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer);
  // ...