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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350