react hook

React Hooks

  • 每次render 都有独立的变量和Effects

1. useState

  • vue - data
data() {
    return {
        count: 0
    }
}
  • react类组件里面的 state
interface CounterStateObj {
    count: number
}
export class Counter extends React.Component <any, CounterStateObj>{
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
    }
}
  • hook
function Counter() {
    const [count, setCount] = useState<number>(0);
    const log = () => {
        setTimeout(() => {
            console.log('2秒后', count)
        }, 2000)
        console.log(count)
    }
    const clickHandler = () => {
        log()
        setTest(count + 1)
    }
    return (
        <div>
            <p>clicked {count} times</p>
            <button onClick={clickHandler}>Click me</button>
        </div>
    );
}
  • Capture Value
页面 log函数
1 0
2 1
3 2
... ...
  • 类比照相机对着一个杯子拍照,第二张杯子碎了,但第一张照片永远是好的(每次setState就是新拍了一张照片,它不会影响前一个capture的状态)
function Example2() {
    const [count, setCount] = useState(0);

    const handleClick = () => {
        setTimeout(() => {
            setCount(count + 1);
        }, 3000);
    };

    return (
        <div>
            <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>
                setCount
            </button>
            <button onClick={handleClick}>
                Delay setCount
            </button>
        </div>
    );
}

操作 先点击第二个按钮, 然后在3秒内连续点击两次第一个按钮

页面结果 0 -> 1 -> 2 -> 1

问题 --- 接着再重复操作呢?

  • 拿到最新的值
setCount(data => data + 1)

2. useRef

  • useRef 返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的ref 对象都是同一个(使用 React.createRef ,每次重新渲染组件都会重新创建 ref)
function Counter() {
    const count = useRef<number>(0);
    const domRef = useRef<HTMLDivElement>()
    const clickHandler = () => {
        count.current++
    }
    return (
        <div>
            <p>You clicked {count.current} times</p>
            <div ref='domRef'></div>
            <button onClick={clickHandler}>Click me</button>
        </div>
    );
}
  • 访问dom
    • vue
      <div ref='aa'></div>
      this.$refs['aa']
    
    • react class
    class MyComponent extends React.Component {
        constructor(props) {
            super(props);
            this.myRef = React.createRef();
        }
        render() {
            return <div ref={this.myRef} />;
        }
    }
    

3. useCallback

  • 依赖不变,返回的是同一个函数
const [contentClassName, setContentClassName] = useState<string>('')
const resizeHandler = useCallback(() => {
         if (contentClassName) {
             setH(contentClassName)
         }
 }, [contentClassName])
    useEffect(() => {
        let event: UEventEmitter = Ioc(UEventEmitter)
        event.on('fullScreenChange', () => {
            resizeHandler()
        })
        window.addEventListener('resize', resizeHandler)
        return () => {
            window.removeEventListener('resize', resizeHandler)
            event.delete('fullScreenChange')
        }
    }, [contentClassName, resizeHandler])

4. useEffect

  • 只要状态更新,它就会根据传入的依赖项决定是否执行, 可以拿到最新的状态
  • react 类组件 里面的 componentDidMount + componentDidUpdate
  • Vue created + updated
  • 与 componentDidMount 或 componentDidUpdate 不同, useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快
// 变化执行
useEffect(() => {

})
// 变化不执行
useEffect(() => {

}, [])

// 只有 test 变化才会执行
useEffect(() => {
  return () => {}
}, [test])

5. useMemo

  • 类比 Vue 的 computed
  • 适合大量计算,缓存值,依赖不变不会重新更新
const [value, setValue] = useState(0);
const increase = useMemo(() => {
    if(value > 2) return value + 1;
}, [value]);
  • React.memo()React.PureComponent区别?

React.memo()适用函数组件,仅仅浅比较props
React.PureComponent类组件,浅比较props和state,根据结果决定是否重新渲染组件

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}

6. useContext

  • 获取context值

7. useReducer

  • 像 redux一样管理数据和行为

8. useLayoutEffect

  • 浏览器 layout 之后,painting 之前执行
  • 可以获取元素样式

react hooks详解实战


轮子

1.实现类似类组件的生命周期

  • componentDidMount
export function useOnMount(fn: () => void, destoryCallBack?: () => void) {
    useEffect(() => {
        fn()
        if (destoryCallBack) {
            return () => {
                destoryCallBack()
            }
        }
    // eslint-disable-next-line
    }, [])
}
  • componentDidUpdate
export function useOnUpdate(fn: () => void, dep?: any[]) {
    const ref = useRef({ fn, mounted: false })
    ref.current.fn = fn
  
    useEffect(() => {
        // 首次渲染不执行
        if (!ref.current.mounted) {
            ref.current.mounted = true
        } else {
            ref.current.fn()
        }
    // eslint-disable-next-line
    }, dep)
}
  • forceUpdate
export function useForceUpdate() {
    const [, setValue] = useState(0)
    return useCallback(() => {
        // 递增state值,强制React进行重新渲染
        setValue(val => (val + 1) % (Number.MAX_SAFE_INTEGER - 1))
      }, [])
}

2.实现多语言

  • 当语言变化的时候 触发一次render
  • 根据当前语言返回对应的值
import { store } from '../store/redux';   // redux
import { sysLanguage } from '../config/langulate';  // 语言配置文件
import { useState, useEffect } from 'react';  
import { LangValue } from '../interface/common';  // 接口
export function useLanguage() {
    const [lang, setLang] = useState<LangValue>(store.getState().user.lang)
    useEffect(() => {
        let unsubscribe = store.subscribe(() => {
            setLang(store.getState().user.lang)
        })
        return () => {
            unsubscribe()
        }
    // eslint-disable-next-line
    }, [])
    return [(cn: string) => {
            if (cn && sysLanguage[lang] && sysLanguage[lang][cn]) {
                return sysLanguage[lang][cn]
            } else {
                return cn || ''
            }
        }
    ]
}

优化

    1. 大量计算 适用useMemo,
    1. 为避免子组件的不必要渲染,useMemo + React.memo() 或 React.PureComponent

例1

function Test() {
    console.log('test render')
    return (
        <div className="test">
            呵呵呵
        </div>
    )
}

export function App() {
    const [test, setTest] = useState(1)
    console.log('app render')
    return (
        <div className='app-container' onClick={() => setTest(test + 1)}>
            { test }
            <Test/>
        </div>
    )
}

每次点击 App 和 Test 各重新渲染一次

例1优化

function TestTem() {
    console.log('test render')
    return (
        <div className="test">
            呵呵呵
        </div>
    )
}
const Test = React.memo(TestTem)
export function App() {
    const [test, setTest] = useState(1)
    console.log('app render')
    return (
        <div className='app-container' onClick={() => setTest(test + 1)}>
            { test }
            <Test/>
        </div>
    )
}

每次点击 只有 App 重新渲染

例2

type ConfigObj = {
    name: number
}
function TestTem(props: {
    config: ConfigObj
}) {
    console.log('test render')
    return (
        <div className="test">
            呵呵呵
            {props.config.name}
        </div>
    )
}
const Test = React.memo(TestTem)
export function App() {
    const [test, setTest] = useState(1)
    const [nameData, setNameData] = useState(1)
    const config: ConfigObj = {
        name: nameData
    }
    const clickHandler = () => {
        setTest(test + 1)
        // setNameData(nameData + 2)
    }
    console.log('app render')
    return (
        <div className='app-container' onClick={clickHandler}>
            { test }
            <Test config={config}/>
        </div>
    )
}

每次点击 App 和 Test 各执行一次

例2优化

type ConfigObj = {
    name: number
}
function TestTem(props: {
    config: ConfigObj
}) {
    console.log('test render')
    return (
        <div className="test">
            呵呵呵
            {props.config.name}
        </div>
    )
}
const Test = React.memo(TestTem)
export function App() {
    const [test, setTest] = useState(1)
    const [nameData, setNameData] = useState(1)
    // 优化
    const config: ConfigObj = useMemo(() => {
        return {
            name: nameData
        }
    }, [nameData])
    const clickHandler = () => {
        setTest(test + 1)
        // setNameData(nameData + 2)  // 放开这句话 会让config 产生变化 Test会重新渲染
    }
    console.log('app render')
    return (
        <div className='app-container' onClick={clickHandler}>
            { test }
            <Test config={config}/>
        </div>
    )
}
    1. 惰性创建昂贵的对象
    const aaa = () => {
        console.log('喀纳斯')
        return 1
    }
    // 每次render,都会执行一遍
    const [test, setTest] = useState<number>(aaa())
    // 只会被执行一次 
    const [test, setTest] = useState<number>(() => aaa())
    1. 在useEffect里面及时解绑事件
    const resizeHandler = useCallback(() => {
            if (contentClassName) {
                setH(contentClassName)
            }
        }, [contentClassName])
    useEffect(() => {
        let event: UEventEmitter = Ioc(UEventEmitter)
        event.on('fullScreenChange', () => {
            resizeHandler()
        })
        window.addEventListener('resize', resizeHandler)
        return () => {
            window.removeEventListener('resize', resizeHandler)
            event.delete('fullScreenChange')
        }
    }, [contentClassName, resizeHandler])
    1. 通用逻辑抽取为自定义hook
    1. 复杂的操作和状态变更,适用useReducer
    1. 避免向下传递回调, 在大型的组件树中,我们推荐的替代方案是通过 context 用 useReducer往下传一个 dispatch 函数:
const TodosDispatch = React.createContext(null);

function TodosApp() {
  // 提示:`dispatch` 不会在重新渲染之间变化
  const [todos, dispatch] = useReducer(todosReducer);

  return (
    <TodosDispatch.Provider value={dispatch}>
      <DeepTree todos={todos} />
    </TodosDispatch.Provider>
  );
}
function DeepChild(props) {
  // 如果我们想要执行一个 action,我们可以从 context 中获取 dispatch。
  const dispatch = useContext(TodosDispatch);

  function handleClick() {
    dispatch({ type: 'add', text: 'hello' });
  }

  return (
    <button onClick={handleClick}>Add todo</button>
  );
}
    1. 每次render 组件执行两次?
export default function Test () {
    const [test, setTest] = useState(1)
    console.log('1111')
    return (
        <div>
            {test}
            <div onClick={() => setTest(test + 1)}>
                23123123
            </div>
        </div>
    )
}

严格模式不能自动检测到你的副作用,但它可以帮助你发现它们,使它们更具确定性。通过故意重复调用以下函数来实现的该操作

严格模式

    1. Hook 会因为在渲染时创建函数而变慢吗?

不会

官方回答

来自知乎社区的回答

** 项目结构


IRIS.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。