React Hooks 入门上

前面的话

实习开始没多久,就开始帮着做一个管理系统的项目, 项目技术战使用 react + ts + antd。在三者之前都没接触的情况下,我硬着头皮上了,class 组件都还没搞明白,就开始了 hooks 之旅。。。

1、Hooks

React Hooks 的意思是: 组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。
React Hooks 就是那些钩子,需要什么功能,就使用什么功能。

2、注意事项

  • 只能在函数组件最外层调用 hooks, 不要在条件、循环、嵌套中调用 hooks
  • 不要在其他 JavaScript 函数中调用

3、useState

class 组件中有 state 状态,而函数组件中不存在。使用 useState 就可以为函数组件引入 state。

const [state, setState] = useState(initialState);
  • useState 的唯一参数是初始值 initialState。
  • useState 返回一个数组,一个是 state,另一个是改变 state 的函数。
  • 在初始渲染时,返回的 state 与初始的参数值一样。
  • 如果想改变这个state,可以通过调用这个函数来改变,不过这里要注意的是:与 class 组件的 this.setState不一样的是,它不会把新的 state 与旧的 state 进行合并,而是直接替换。
  • 调用状态更新函数后,React 重新渲染组件,以使新状态变为当前状态。
3.1 使用例子
interface Child1Props {
  num: number;
  setNum: Dispatch<SetStateAction<number>>;
}

interface Child2Props {
  text: string;
  setText: Dispatch<SetStateAction<string>>;
}

const Child1 = (props: Child1Props) => {
  const {num, setNum} = props;
  return (
    <div>
      <button onClick={() => setNum(num + 1)}>{num}</button>
    </div>
  );
};
const Child2 = (props: Child2Props) => {
  const {text} = props;
  return (
    <div>
      <span>input: {text}</span>
    </div>
  );
};

const App = () => {
  const [num, setNum] = useState<number>(0);
  const [text, setText] = useState<string>('');
  return (
    <div>
      <Child1 num={num} setNum={setNum} />
      <Child2 text={text} setText={setText} />
      <input type='text' value={text} onChange={e => setText(e.target.value)} />
    </div>
  );
};
3.2 使用回调更新状态

如果更新的状态需要依赖与前一个state状态,那么可以使用回调来更新状态:

setState(preState => nextState);

例子:

const App = () => {
  const [on, setOn] = useState(false);

  return (
    <>
      <span>{on ? '开' : '关'}</span>
      <button onClick={() => setOn(on => !on)}>{'on/off'}</button>
    </>
  );
};
3.3 惰性的初始化
  • 初始的 state 只会在初始化渲染时,才会起作用,之后的渲染会被忽略。
  • 如果初始的 state 需要通过复杂的计算才能得到,那么可以传入一个函数,该函数只在初次渲染时执行,之后会被忽略。

例子:

someExpensiveComputation是一个相对耗时的操作,如果我们直接使用:

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

这样每次函数被渲染时,都会执行这个 someExpensiveComputation 耗时的操作。 如果使用惰性初始化的方法:

const [state, setState] = useState(()=>someExpensiveComputation(props));

这样只会在初次渲染时执行someExpensiveComputation这个函数,从而性能优化。

3.4 多个 useState 相互独立

假如一个函数组件有多个状态: 每次 useState 里面都只传一个值,并没有告诉没个值对应的 key 是哪一个,react 如何保证多个 useState 是相互独立的?

答案是: 根据 useState 的出现顺序来决定的。

const TabsBasic = () => {
	const [loading, setLoading] = useState<boolean>(false);
	const [name, setName] = useState<string>('xiaoqi');
	const [num, setNum] = useState<number>(1);
	// ...
}

第一次渲染:

 useState<boolean>(false); //  将 loading 值设置为 false
 useState<string>('xiaoqi'); // 将 name 设置为 xiaoqi
 useState<number>(1);// 将 num 设置为 1

第二次渲染:

 useState<boolean>(false); //  将 loading 值设置为读取的新值
 useState<string>('xiaoqi'); // 同上
 useState<number>(1);//  同上

如果将代码修改一下:

   const TabsBasic = () => {
   let  show = true;
	const [loading, setLoading] = useState<boolean>(false);
	if(show) {
		const [name, setName] = useState<string>('xiaoqi');
		show = false;
	}
	const [num, setNum] = useState<number>(1);
	// ...
}

再来看一下渲染情况:

第一次渲染:

 useState<boolean>(false); //  将 loading 值设置为 false
 useState<string>('xiaoqi'); // 将 name 设置为 xiaoqi
 useState<number>(1);// 将 num 设置为 1

第二次渲染:

 useState<boolean>(false); //  将 loading 值设置为读取的新值
//  useState<string>('xiaoqi'); 
 useState<number>(1);//  读取的值是 name 的值,报错

所以前面说的必须把 hooks 放在最外层,不要在条件、循环、嵌套中调用 hooks。

3.5 性能优化
  • 与 class 组件不同的是,当传入的值没有发生变化时,组件不会重新渲染。
  • 与 class 组件不同的是,传入的值是直接替换,而不是合并,所以要使用扩张运算符。
const App = () => { 
  const [counter, setCounter] = useState({name: '计数器', number: 0});
  return (
    <>
      <p>
        {counter.name} : {counter.number}
      </p>
      <button
        onClick={() => setCounter({...counter, number: counter.number + 1})}>
        +
      </button>
      // 不会重新渲染
      <button onClick={() => setCounter({...counter})}>++</button>
    </>
  );
};

4、useEffect

useEffect 用于处理副作用函数。

  • effect 副作用: 比如 ajax 请求、操作原生DOM 元素、绑定/解绑事件、添加订阅、设置定时器、本地持久化缓存等
  • useEffect 接受一个函数,该函数会在组件渲染到屏幕之后才执行,该函数要么返回一个清楚副作用的函数,要么就什么不返回。
  • useEffect 与 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount具有相同的用途
  • useEffect 在浏览器渲染完毕之后才会执行,所以不会阻塞视图的更新。
4.1 使用例子
const Counter = () => {
	const [count, setCount] = useState(0);
   useEffect(()=>{
   		setTimtout(()=>{
   			console.log(count)
   		},3000)
   })
	return (
		 <div>
		   <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>
                Click me
        </button>
        </div>
	)
}

每次重新渲染组件时,都会执行一次 useEffect 函数。

⚠️注意: useEffect 只接受函数, 所以一下写法是不正确的:

 // ❌因为async返回的是个promise对象 

 useEffect(async() => { 
  const data = await getAjax()
  }) 

 // 建议😄 
  useEffect(() => {    
   // 利用匿名函数  
      (async()=>{  const data = await getAjax() })()
  })
4.2 清除副作用

在 useEffect 中返回一个函数,用于清除副作用,这个函数会在组件卸载之前执行。

const Counter = () => {
  const [num, setNum] = useState<number>(0);
  const [text, setText] = useState<string>('');
  useEffect(() => {
    let timer = setInterval(() => {
      setNum(num => num + 1);
    }, 1000);

    return () => {
      console.log('clear');
      clearInterval(timer);
    };
  });

  return (
    <>
      <input
        type='text'
        value={text}
        onChange={event => setText(event.target.value)}
      />
      <p>{num}</p>
      <button>+</button>
    </>
  );
};

在每一次setNum 之后组件都会重新渲染,每次重新渲染之前都会清除定时器。

跳过不必要的副作用

如果一个副作用函数只想再初次渲染时执行,可以给useEffect 传第二个参数。用第二个参数来告诉react 只有当这个依赖值发生变化时,才执行副作用函数。当第二个参数为[]时,表示只在初次渲染时执行。

// 初次渲染时才执行
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, []);  
//   只有当count的值发生变化时,才会重新执行
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值