• 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.

  • 相关阅读:
    linux fork, system, exec()
    wiegand 问题
    route/ip route的作用
    The Name/Origin of Country names
    修改 timezone
    Socket 广播
    IC卡的逻辑卡号和市民卡卡号
    32位和64位程序
    短连接和长连接
    各个公司的来源/The etymology of company
  • 原文地址:https://www.cnblogs.com/ceceliahappycoding/p/12433492.html
Copyright © 2020-2023  润新知