React Hooks

212 阅读6分钟

react函数组件

创建

function MyComponent(){
    return (
        <div>
            创建组件
        </div>
    )
}

Hooks

useState 代替class组件state中的

function Parent() {
  const [name, setName] = useState('小明')//useState,返回数据和修改数据的aip
  return (
    <div>
      父组件的state:{name}
      <button onClick={()=>setName('小黑')}>
        改名字
      </button>
    </div>
  )
}
//useState接受函数,setState也接受函数
function App() {
    const [user, setName] = useState(() => ({ name: '用户名', age: 18 }))
    return (
    <div>
      <p>{user.name}</p>
      <button onClick={() => setName(state => ({ ...state, name: '新用户名' }))}>改名字</button>
    </div>
  )
}

useEffect 模拟生命周期

//不加第二个参数,任意数据发生变化都会执行
function Parent() {
  const [N, setN] = useState(0)
 
  useEffect(() => {
    console.log('挂载后')
  },[])//模拟componentDidMount(挂载后) 在useEffect第二个参数接[]
  
  return (
    <div>
      父组件的state:{N}
      <button onClick={() => setN(N + 1)}>
        改名字
      </button>
    </div>
  )
}

模拟组件消亡

function Parent() {
  const [childVisible, setChildVisible] = useState(true)
  useEffect(() => {
    console.log('渲染')
    return function () {//模拟componentWillUnmount (组件消亡前)在useEffect回调函数后
      console.log('组件即将消亡')
    }
  })
  const reverse = () => {
    setChildVisible(!childVisible)
  }
  return (
    <div>
      {childVisible ? <button onClick={reverse}>show</button> : <button onClick={reverse}>hide</button>}
      {childVisible ? <Child /> : ''}
    </div>
  )
}
const Child = props => {
  return (
    <div>
      子组件
    </div>
  )
}

模拟componentDidUpdate

useEffect(() => {
    console.log('数据更新l')
},[N])//模拟componentDidUpdata(数据更新了) 在useEffect第二个参数接[n],n每次变化会执行回调
//模拟componentDidUpdata 从第二次更新数据执行回调
//自定义Hooks
//模拟componentDidUpdate ,页面更新了执行回调
const [N, setN] = useState(0)//依赖n 
 
//自定义Hook
const useX = (callback, dep) => {//接受一个,回调和依赖项
    const [count, setCount] = useState(0)//用来计算渲染次数,排除第一次挂载页面
    useEffect(() => {
        setCount(x => x + 1)
    }, [dep])//每次依赖发生变化,+1
    
    useEffect(() => {//如果第二次渲染触发回调
        if (count > 1) {
            callback.call(null)
        }
    }, [count, callback])
}
useX(() => console.log('更新了'), N)//调用

useLayoutEffect

useLayoutEffect在浏览器渲染前之前执行
useLayoutEffect总是比useEffect先执行
useLayoutEffect里的人物最好影响了Layout

function App() {
    const [n, setN] = useState(0)
    useLayoutEffect(() => {
        document.querySelector('#a').innerText = 'value:10000'
    }, [n])
    return (
        <div>
            xxxx
            <p id="a">{n}</p>
        </div>
    )
}

useState 原理

useState
如上面代码所示,state接受值,返回新的值和操作它的setState,其中数组和顺序很重要,因为我们每次点击都会重新执行App函数,所以需要用数组来记录每次传入的值,这个数组是一个闭包,所以我们每次进入函数都可以用index拿到最新的值,所以React useState不能写在if 条件语句中,这样会打乱数据的顺序,导致bug
函数式编程讲究数据不可变
所以我们每一次的数据都是不同的数据

useReducer

使用useReducer一般分为4部

  1. 创建初始值initialState
  2. 创建所有操作reducer(state,action)
  3. 传给useReducer,得到读和写的API
  4. 调用({type:'操作类型',data:data})
const initFormData = {
    name: "",
    age: 18,
    nationality: "汉族"
}
const reducer = (state, action) => {
    if (action.type === 'update') {
        return { ...state, ...action.formData }
    }
    else if (action.type === 'reset') {
        return initFormData
    }
    else {
        return state
    }
}
function App() {
    const [state, dispatch] = useReducer(reducer, initFormData)
    const onSubmit = (e) => {
        console.log(e)
        }
    const xxx = () => console.log('xxx')
    return (
        <div>
            <form onSubmit={onSubmit} onReset={xxx}>
                {state.n}
                <section>
                    姓名:<input type="text" value={state.name} onChange={
                        (e) => dispatch({ type: 'update', formData: { name: e.target.value } },)
                    } />
                </section>
                <section>
                    年龄:<input type="text" value={state.age} onChange={
                        (e) => dispatch({ type: 'update', formData: { age: e.target.value } },)
                    } />
                </section>
                <section>
                    名族:<input type="text" value={state.nationality} onChange={
                        (e) => dispatch({ type: 'update', formData: { nationality: e.target.value } })
                    } />
                </section>
                <div>
                    <button type="onSubmit">提交</button>
                    <button type='onReset'>重置</button>
                </div>
            </form>
        </div>
    )
}

使用useReducer代理Redux

  1. 将数据集中在一个store对象
  2. 将所有操作集中在reducer
  3. 创建一个Context
  4. 将第四步的内容放到第三步的Context
  5. 用Context.Provider将Context提供给所有组件
  6. 各个组件用useContext获取读写Api
//例子
import React, { useReducer, createContext, useContext, useEffect } from 'react'

const store = {//初始化仓库
    user: null,
    books: null,
    movies: null
}
const reducer = (state, action) => {
    switch (action.type) {
        case 'setUser':
            const data = { ...state, user: action.user }
            console.log(data)
            return data
        case 'setBooks':
            const data2 = { ...state, books: action.books }
            console.log(data2)
            return data2
        case 'setMovies':
            return { ...state, movies: action.movies }
        default:
            throw new Error()
    }
}

const Context = createContext(null)
function App() {
    const [state, dispatch] = useReducer(reducer, store)
    return (
        <Context.Provider value={{ state, dispatch }}>
            <User />
            <hr />
            <Books />
            <hr />
            <Movies />
        </Context.Provider>
    )
}
function User() {
    const { state, dispatch } = useContext(Context)
    useEffect(() => {
        ajax('user').then(res => {
            console.log(res)
            dispatch({ type: 'setUser', user: res })
        })
    }, [])
    return (
        <div>
            用户名:{state.user ? state.user.name : '请稍等'}
        </div>
    )
}
function Books() {
    const { state, dispatch } = useContext(Context)
    useEffect(() => {
        ajax('books').then(res => dispatch({ type: 'setBooks', books: res }))
    }, [])
    return (
        <div>
            <h2>books</h2>
            {state.books ? state.books.map(item => <li key={item.id}>{item.name}</li>) : '请稍等'}
        </div>
    )
}
function Movies(){
    const {state,dispatch} = useContext(Context)
    useEffect(()=>{
        ajax('movies').then(res=>dispatch({type:'setMovies',movies:res}))
    },[])
    return (
        <div>
            {state.movies ? state.movies.map(item => <li key={item.id}>{item.name}</li>) : '请稍等'}
        </div>
    )
}
function ajax(string) {
    return new Promise((resolve, reject) => {
        switch (string) {
            case 'user':
                setTimeout(() => {
                    resolve({
                        id: 1,
                        name: '小白'
                    })
                }, 3000)
            case 'books':
                setTimeout(() => {
                    resolve([
                        { name: '蝴蝶书', id: 1 },
                        { name: '编程艺术', id: 2 }
                    ])
                }, 3000)
            case 'movies':
                setTimeout(() => {
                    resolve([
                        {
                            name: '两杆大烟枪',
                            id: 1
                        },
                        {
                            name: '小兵张嘎',
                            id: 2
                        }
                    ])
                }, 3000)
        }
    })
}

export default App

useMemo

React.memo可以模拟shouldeComponentUpdate

function App() {
    const [n, setN] = useState(0)
    const [m, setM] = useState(0)
    const onClick = () => {
        setN(state => state + 1)
    }
    const onClick2 = useMemo(() => {
        return () => setM(state => state + 1)//m变化再次缓存结果
    }, [m])//如果依赖m发生变化话才冲洗执行<M />组件的内容


    return (
        <div>
            {n}
            <hr />
            <button onClick={onClick}>加N</button>
            <M value={m} onClick={onClick2} />
        </div>
    )
}
const M = React.memo((props) => {
    console.log('执行了')
    console.log(props)
    return (
        <div>
            {props.value}
            <hr />
            <button onClick={props.onClick}>加M</button>
        </div>
    )
})
//这样写有问题,如果传进来的是一个对象,或者一个函数(因为函数每次的地址不同)React,不会帮我们阻止多余的渲染,所以需要我们用useMemo,来设定当某个值发生变化了再执行
//或者用下面这种写法也可以
const MyComponent = React.memo(
    _MyComponent, 
    (prevProps, nextProps) => nextProps.count !== prevProps.count//检查props
)

useMome语法糖useCallback

useCallback(x=>console.log(x),[依赖])
useMemo(()=>x=>console.log(x),[m])
两种写法等价

useRef

用法:如果我们需要一个值在组件不断render的时候保持不变
Ref不会自动render

function App() {
    const [n, setN] = useState(0)
    const count = useRef({ value: 0 })//创建
    console.log(count)
    const onClick = () => {
        setN(state => state + 10)
    }
    useEffect(() => {
        count.current.value += 1//记录n变化的次数
        console.log(count.current.value)
    }, [n])
    return (
        <div>
            {n}
            <hr />
            <button onClick={onClick}>加N</button>
        </div>
    )
}

frowardRef实现函数组件ref

props无法传递ref属性,所以用frowardRef包裹住该组件

function App() {
    const count = useRef()//创建
    return (
        <div>
            {n}
            <hr />
            <Son ref={count} >加N</Son>
        </div>
    )
}
const Son = forwardRef((props, ref) => {
    useEffect(() => {
        console.log(props, ref)
    }, [])
    return (
        <div ref={ref}>
            xxx
        </div>
    )
})

useImperativeHandle (setRef)

//一般用于封装一个Ref
function App() {
    const count = useRef(null)//创建
    useEffect(() => {
        console.log(count.current)
    }, [])
    return (
        <div>

            <Son ref={count} >加N</Son>
        </div>
    )
}
const Son = forwardRef((props, ref) => {
    const realButton = createRef(null)
    useImperativeHandle(ref, () => ({
        x: () => realButton.current.remove(),//添加一些属性和功能
        realButton//返回当前button
    }))
    return (
        <div ref={realButton}>
            xxx
        </div>
    )
})

自定义Hooks

export default (initState) => {
    const [list, setList] = useState(initState)
    useEffect(() => {
        ajax('/list').then(res => setList(res))
    }, [])
    const deleteList = (deleteIndex) => {
        setList(state => state.filter((item, index) => index !== deleteIndex))
    }
    return { list, setList, deleteList }
}
function ajax(path) {
    return new Promise((resolve, reject) => {
        if (path === '/list') {
            setTimeout(() => {
                resolve([
                    { id: 1, name: '小明' },
                    { id: 2, name: '小黑' },
                    { id: 3, name: '杨小气' },
                    { id: 4, name: '小仙女' },
                    { id: 5, name: '杨狗蛋' }
                ])
            })
        }
    })
}