image.png

01|认识 React:如何创建你的第一个 React 应用?

  • 组件
    • 内置组件:映射到HTML的节点组件
    • 自定义组件:自己创建的组件
  • 状态
    • state:管理组件内部状态
    • props:组件外部传入的状态
  • JSX
    • 语法糖(JSX 不是一种新的概念,只是原生JS的另外一种写法) ```jsx function Counter() { const [count, setCount] = React.useState(0); return (
      ); }

React.createElement( “div”, null, React.createElement( “button”, { onClick: function onClick(){ return setCount(count + 1); } }, React.createElement(CountLabel, {count: count}) ) )

  1. <a name="pcY3c"></a>
  2. # 02|理解 Hooks:React 为什么要发明 Hooks?
  3. <a name="qHOKc"></a>
  4. ### 类组件 VS 函数组件
  5. 使用类组件的弊端(class的优势没有发挥的空间)
  6. - 组件之间不会相互集成
  7. - 组件的方法不会被外部调用
  8. 函数组件<br />UI = render(state)
  9. <a name="GJo0e"></a>
  10. ### Hooks
  11. > hooks解决的本质问题,就是跟纯函数组件提供状态管理的能力,通过提供外部的数据源,当数据发生变化时,组件渲染,但是在多次渲染期间,可以保持外部数据的状态
  12. 如何实现HOOKS?<br />Hooks就是把某个 **目标结果(函数组件)**钩到可能变化的 **数据源或者事件源上(state、url、window size)**,那么当被钩到的数据源或者事件发生变化时,产生目标结果的代码会重新执行,产生更新后的结果(**view视图**)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/152077/1661930704465-77f24462-5950-47f3-b45e-116a2943b5d4.png#clientId=ufba0bf06-af47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=552&id=u96d46df8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=552&originWidth=1110&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33378&status=done&style=none&taskId=u762bc9ac-4a30-40f1-85d3-ec69403c8e7&title=&width=1110)
  13. Hooks的优势
  14. - 简化逻辑复用
  15. - HOC
  16. - render props
  17. - 有助于关注点分离
  18. <a name="ltUPS"></a>
  19. # 03|内置 Hooks(1):如何保存组件状态和使用生命周期?
  20. <a name="hUmMd"></a>
  21. # 04|内置 Hooks(2):为什么要避免重复定义回调函数?
  22. <a name="fcA1W"></a>
  23. ### useState
  24. - 定义一个state,并提供一个修改的方法
  25. ```javascript
  26. const [count, setCount] = useState(0)

注意事项

  • state 不要存放能够通过计算得到的值

useEffect

用于执行一段副作用(一段和当前执行结果无关的代码)
在函数组件当次执行的过程中,useEffect中的代码的执行是不影响渲染出来的UI的

使用场景:
每次render后都执行(不提供第二个依赖项)

  1. useEffect(()=>{
  2. // dosomething
  3. })

仅第一次render后执行(提供一个空的数组)

  1. useEffect(()=>{
  2. // dosomething
  3. }, [])

第一次render后以及依赖项发生变化后执行(提供依赖数组)

  1. useEffect(()=>{
  2. // dosomething
  3. }, [deps])

组件unmount后执行

  1. useEffect(()=>{
  2. return ()=>{}
  3. },[])

useCallback

缓存回调函数

  1. useCallback(fn, deps)

useMemo

缓存计算的结果

  1. useMemo(fn, deps)

useRef

  • 用于在多次渲染之间共享数据
  • 活动元素的真实DOM(input 获得焦点)
    1. const refContainer = useRef(initVal)

useContext

让react 函数组件具备了定义全局的响应式数据的能力

  • 创建一个 context管理文件 并导出该上下文 CounterContext

    1. import { createContext } from 'react';
    2. export const CounterContext = createContext();
  • 父组件通过value提供传递下去的数据,并包裹子组件

    1. <CounterContext.Provider value={{count, setCount}}>
    2. <Counter/>
    3. </CounterContext.Provider>
  • 子组建消费父组件传递的数据

    1. const value =useContext(CounterContext);

05|进一步认识 Hooks :如何正确理解函数组件的生命周期?

类组件 VS 函数组件

类组件:在某个生命周期我要做什么
函数组件:当某个状态发生变化时,我要做什么

构造函数 constructor

类组件:在其他代码执行之前的一次性初始化工作
函数组件:一次性的代码执行

  1. import {useRef} from 'react';
  2. function useSingleton(callback){
  3. const called = useRef(false);
  4. if(called.current){
  5. return;
  6. }
  7. callback();
  8. called.current = true;
  9. }
  10. // demo
  11. import useSingleton from './useSingleton';
  12. const MyComp = () => {
  13. // 使用自定义 Hook
  14. useSingleton(() => {
  15. console.log('这段代码只执行一次');
  16. });
  17. return (
  18. <div>My Component</div>
  19. );
  20. };

三种常用的生命周期方法

  1. useEffect(() => {
  2. // componentDidMount + componentDidUpdate
  3. console.log('这里基本等价于 componentDidMount + componentDidUpdate');
  4. return () => {
  5. // componentWillUnmount
  6. console.log('这里基本等价于 componentWillUnmount');
  7. }
  8. }, [deps])

Class 组件中还有其它一些比较少用的方法,比如 getSnapshotBeforeUpdate, componentDidCatch, getDerivedStateFromError 目前在hooks还无法实现

06|自定义Hooks :四个典型的使用场景

抽取业务逻辑

  1. import { useCallback, useState } from 'react';
  2. const useCounter = (step) =>{
  3. const [counter, setCounter] = useState(0);
  4. // 加一
  5. const increment = useCallback(()=>{
  6. setCounter(counter + step);
  7. }, [counter, step])
  8. // 减一
  9. const reducer = useCallback(()=>{
  10. setCounter(counter - step);
  11. }, [counter, step])
  12. return {counter, increment, reducer}
  13. }
  14. export default useCounter;
  1. import {useState} from 'react';
  2. import useCounter from './useCounter';
  3. const Counter = ()=>{
  4. const [setp, setStep] = useState(1);
  5. const {counter, increment, reducer} = useCounter(setp);
  6. return <div>
  7. {counter}
  8. <button onClick={increment}>add</button>
  9. <button onClick={reducer}>reducer</button>
  10. <button onClick={()=>{
  11. setStep(Math.floor(Math.random() * 10))
  12. }}>change setp {setp}</button>
  13. </div>
  14. }
  15. export default Counter;

封装通用逻辑
利用了 Hooks 能够管理 React 组件状态的能力,将一个组件中的某一部分状态独立出来,从而实现了通用逻辑的重用

  1. import { useCallback } from 'react';
  2. import {useState} from 'react';
  3. const useAsync = (execFun)=>{
  4. const [data, setData] = useState([]);
  5. const [error, setError] = useState(null);
  6. const [loading, setLoading] = useState(false);
  7. const fetchFn = useCallback(()=>{
  8. setLoading(true);
  9. setError(null);
  10. return execFun().then((res)=>{
  11. setData(res);
  12. }).catch((err)=>{
  13. setError(err);
  14. }).finally(()=>{
  15. setLoading(false);
  16. })
  17. }, [execFun]);
  18. return {data, error, loading, fetchFn};
  19. }
  20. export default useAsync;
  1. const fetchUsers = async ()=>{
  2. const res = await fetch("https://reqres.in/api/users/");
  3. const json = await res.json();
  4. return json.data;
  5. }
  6. const {data, error, loading, fetchFn} = useAsync(fetchUsers);

监听浏览器状态
可以让 React 的组件绑定在任何可能的数据源上。这样当数据源发生变化时,组件能够自动刷新

  1. import { useEffect, useState } from 'react';
  2. const getScrollPosition = ()=>{
  3. return {
  4. x: document.documentElement.scrollLeft || document.body.scrollLeft,
  5. y: document.documentElement.scrollTop || document.body.scrollLeft,
  6. }
  7. }
  8. const useScroll = ()=>{
  9. const [position, setPosition] = useState(getScrollPosition());
  10. useEffect(()=>{
  11. const handler = ()=>{
  12. setPosition(getScrollPosition(document));
  13. }
  14. document.addEventListener('scroll', handler)
  15. return ()=>{
  16. document.body.removeEventListener('scroll', handler)
  17. }
  18. }, [])
  19. return position;
  20. }
  21. export default useScroll;

拆分复杂组件

07|全局状态管理:如何在函数组件中使用 Redux?


image.png

redux的三个概念

  • store
  • reducer
  • action

image.png

异步Action

使用模式,通过组合同步的Action,用一致的方案提供了处理异步逻辑的方案

Redux提供了一个middleware的机制实现异步的action
middleware 可以让你提供一个拦截器在 reducer 处理 action 之前被调用。在这个拦截器中,你可以自由处理获得的 action。无论是把这个 action 直接传递到 reducer,或者构建新的 action 发送到 reducer,都是可以的。
image.png

redux-thunk使用

Redux中的Action不仅可以是一个Object,也可以是一个函数;如果发现接受的action是函数的时候,那么不会传递给Reducer,而是先执行这个函数,并把dispatch作为参数传递给这个函数,从而在函数中可以自由的决定何时、如何的发送action

  1. 创建 Redux Store 时指定了 redux-thunk ```javascript mport { createStore, applyMiddleware } from ‘redux’ import thunkMiddleware from ‘redux-thunk’ import rootReducer from ‘./reducer’

const composedEnhancer = applyMiddleware(thunkMiddleware) const store = createStore(rootReducer, composedEnhancer)

  1. 2. dispatch action 时就可以 dispatch 一个函数用于来发送请求
  2. ```javascript
  3. function fetchData() {
  4. return dispatch => {
  5. dispatch({ type: 'FETCH_DATA_BEGIN' });
  6. fetch('/some-url').then(res => {
  7. dispatch({ type: 'FETCH_DATA_SUCCESS', data: res });
  8. }).catch(err => {
  9. dispatch({ type: 'FETCH_DATA_FAILURE', error: err });
  10. })
  11. }
  12. }
  1. dispatch action 时就可以 dispatch 一个函数用于来发送请求 ```javascript import fetchData from ‘./fetchData’;

function DataList() { const dispatch = useDispatch(); // dispatch 了一个函数由 redux-thunk 中间件去执行 dispatch(fetchData()); } ```