react hooks

零:hook解决的问题:

避免地狱式嵌套,内部无法判断props是来源,可读性高;

class组件生命周期太多太复杂,使函数组件存在状态,带组件状态的逻辑很难重用;

ui和逻辑更容易分离.

一、hooks的使用

1、useState:

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

setState函数用于更新state,接受一个新的state的值,并将组件的一次重新渲染加入队列.

传统的Class Component:

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>
        )
    }
}
import React,{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>
    ) 
}

useState方法的入参只有一个就是初始值,这个初始值可以是一个数字,字符串或对象,甚至可以是一个函数,当入参是函数时,函数只会在这个组件初始渲染的时候执行.

useState的返回值是一个数组,第一项是state当前值,第二项是改变state的方法.
更新state的方式:
    setCount(count+1)
    或者
    setCount(preCount => {
        return preCount+1
    })

两者的区别:

function Counter() {
  const [count, setCount] = useState(0);
//快速点击的时候只+1次1
  function handleClick() {
    setTimeout(() => {
      setCount(count + 1)
    }, 3000);
  }
// 快速点击的时候每次都会+1
  function handleClickFn() {
    setTimeout(() => {
      setCount((prevCount) => {
        return prevCount + 1
      })
    }, 3000);
  }
  return (
    <>
      Count: {count}
      <button onClick={handleClick}>+</button>
      <button onClick={handleClickFn}>+</button>
    </>
  );
}

注意:如果state是一个对象,setState的时候不会向Class Component的setState那样自动合并对象,如果需要合并,需要:

setState(prevState => {
    return {...prevState,...updatedValues};
})

setState的参数除了数字,字符串或对象,还可以是函数.与Class Component的setState很像的一点是,当传入的值跟之前的值一样时(使用Object.is比较),不会触发更新.

2、useEffect:

会在每次DOM渲染后执行,不会阻塞页面渲染,具备

componentDidMount,

componentDidUpdate,

componentWillUnmount三个生命周期函数的执行时机

useEffect实现componentDidMount:

import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [search, setSearch] = useState('');
 
  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://localhost/api/v1/search?query=${query}`,
      );
 
      setData(result.data);
    };
 
    fetchData();
  }, [search]);
 
  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button type="button" onClick={() => setSearch(query)}>
        Search
      </button>
 
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}
 
export default App;

useEffect实现componentDidUpdate:(先执行一次,当count改变的时候再执行)

//仅当count改变的时候才执行Effect
useEffect(async () => {
    ...
},[count]);

useState修改数据会触发页面重新渲染,页面重新渲染后,会根据情况执行useEffect

应用:

自定义hooks,引用封装好的hooks方便复用解耦.

const useHackerNewsApi = () => {
  ...
 
  useEffect(
    ...
  );
 
  const doFetch = url => {
    setUrl(url);
  };
 
  return { data, isLoading, isError, doFetch };
};
 
function App() {
  const [query, setQuery] = useState('redux');
  const { data, isLoading, isError, doFetch } = useHackerNewsApi();
 
  return (
    <Fragment>
      <form
        onSubmit={event => {
          doFetch(
            `http://localhost/api/v1/search?query=${query}`,
          );
 
          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>
 
      ...
    </Fragment>
  );
}

跟 useState 一样,你可以在组件中多次使用 useEffect.通过使用 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里:

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

3、useLayoutEffect:

用法跟useEffect用法是一样的,唯一的区别是执行的时机.

useLayoutEffect会阻塞页面渲染,如果在其中执行耗时的任务的话,会卡顿.绝大多数下,useEffect是更好的选择,除非:需要根据新的ui进行DOM操作,useLayoutEffect会保证在页面渲染前执行,而useEffect会因为渲染了两次而出现抖动

4、useContext:

如果想让Navbar组件和Messages组件共享数据,就会用到context.

const TestContext = React.createContext({});
<TestContext.Provider 
	value={{
		username: 'superawesome',
	}}
>
	<div className="test">
		<Navbar />
		<Messages />
	</div>
<TestContext.Provider/>
//navbar组件使用公共数据如下
const Navbar = () => {
	const { username } = useContext(TestContext);
	return (
		<div className="navbar">
			<p>{username}</p>
		</div>
	)
}

5、useReducer:

useReducer的用法跟 Redux 非常相似,当 state 的计算逻辑比较复杂又或者需要根据以前的值来计算时,使用这个 Hook 比useState会更好

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}) {
// 第一个参数是reducer,用dispatch触发
// 第二个参数是初始state的值
// 第三个参数是惰性初始化state,这样初始 state 将被设置为 init(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: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

6、useCallback/useMemo/React.memo:可以在依赖不变的情况下,避免不必要的重复渲染

useMemo:缓存变量

import React from 'react';
 
 
export default function WithoutMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
// 每次都需要进行重复计算
    function expensive() {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }
 
    return <div>
        <h4>{count}-{val}-{expensive()}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

可以改成:只有count改变的时候才会重新计算,否则返回上一次计算的值

const expensive = useMemo(() => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }, [count]);

useCallback缓存函数:const fnA = useCallback(fnB, [a]),当a变化时返回fnB

import React, { useState, useCallback } from 'react';
 
export default function Callback() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        console.log(count);
    }, [count]);
 
    return <div>
        <h4>{count}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}

7、React.memo:相当于shouldComponentUpdate方法,区别是它只能比较props,不会比较state

const Parent = React.memo(({ a, b }) => {
  // 当 a 改变时才会重新渲染
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // 当 b 改变时才会重新渲染
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
});

唯一的区别是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件。

8、useRef:

之前的Class Component获取ref的方式如下:

class MyComponent extends React.Component{
    constructor(props){
        super(props);
        this.myRef = React.createRef();
    }
    componentDidMount(){
        this.myRef.current.focus();
    }
    render(){
        return <input ref={this.myRef} type='text'/>
    }   
}

hooks的实现方式如下:

function MyComponent(){
    const myRef = useRef();
    useEffect(()=>{
        myRef.current.focus();
    })
    return <input ref={myRef} type='text'/>
}

useRef返回的是一个普通的js对象,可以将任意数据存到current属性里,

useRef可以跨越渲染周期存储数据,而且对他的修改也不会引起组件渲染.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值