React-hooks

·  阅读 603

useEffect

  • useEffect默认相当于DidMount和Didupdate生命周期
  • useEffect第二个参数设置为[],则useEffect默认只执行一次,相当于DidMount
  • 可以模拟Didupdate生命周期,第二个参数的数组里面设置需要监听的值的改变
  • 返回应该函数,函数里面的内容相当于是WillUnMount生命周期,在里面执行一些,清除定时器,清除自定 特别注意 当useEffect第二个参数没有设置的时候,useEffect即能模拟DidMount,又能模拟Didupdate
  • 当页面的值改变的时候,相当于触发update,这时,还要执行,返回的函数的内容WillUnMount,这时,获取的值,是没有改变的值,这是由于闭包的特性,保留了上次的值
const TestFn = () => {
    const [age, setAge] = useState(22)

    useEffect(() => {
        console.log('执行useEffect', age)
        return () => {
            console.log('模拟组件销毁', age)
        }
    })

    const handleClick = () => {
        setAge(age+3)
    }

    return (
        <Fragment>
            <div>{age}</div>
            <button onClick={handleClick}>点击</button>
        </Fragment>
    )
}

export default TestFn
复制代码

memo, useMemo, useCallback

  • 主要用来做性能优化
  • 当父组件的值发生变法时,子组件的也要重新渲染,造成不必要的性能浪费
  • 操作方法,子组件用memo函数包裹返回,父组件去监听子组件有哪些值变法才去渲染子组件
  • 传递的有函数用useCallback,传递的有值,用useMemo,第二个参数是一个数组,看那些值有变化,才去执行,useCallback第二个参数默认是一个空数组
  • useCallback主要是为了父组件传递给子组件时,传递了函数过去,默认传递了函数过去,使用了useMemo和memo子组件还是会重新渲染,所以用useCallback是为了让子组件不重新渲染,
  • memo默认是浅比较,所以当父组件传递引用类型(对象,数组)给子组件时,子组件还是会重新渲染,所有用useMemo的第二个参数判断哪些值,改变了才进行重新渲染
//默认是浅比较和React.# PureComponent一样
import React, { useState, memo, useMemo, useCallback } from 'react'

const Child = memo(({userInfo}) => {
    console.log(99)
    return (
        <div>
            Child
            {userInfo.age}
        </div>
    )
})


const TestUseMemo = () => {
    const [count, setCount] = useState(0)
    const [age, setAge] = useState(1)

    const onClick = () => {
        setCount(count + 1)
   
    }

    const changeChildVal = () => {
        setAge(age + 1)
    }

    const handleClick = useCallback(() => {
        console.log('handleClick')
    }, [])
    
    const userInfo = useMemo(() => {
        return {
            age, 
            city: '成都'
        }
    }, [age])


    return (
        <div>
            <div>{count}</div>
            <div>{age}</div>
            <button onClick={onClick}>点击</button>
            <button onClick={changeChildVal}>点击2</button>
            <Child handleClick={handleClick} userInfo={userInfo} />
        </div>
    )
}

export default TestUseMemo
复制代码

hooks使用规范

  • 只能用于react函数组件中和自定义hooks中,其他地方不可以
  • 只能用于顶层代码,不能用于循环,判断中
  • eslint-plugin-react-hooks

插槽

默认插槽

<MainFn testMainClick={testMainClick} >
    <div>
      我是默认slot
    </div>
</MainFn>
//使用
const MainFn = ({children, testMainClick}) => {
  const handleMain = () => {
    testMainClick('AAA')
  }

  return (
    <div>
      <button onClick={handleMain}>MainFn</button>
      <div>
        {
          children //默认插槽直接把children渲染上去就行了,默认就是一个jsx,代码片段
        }
      </div>
    </div>
  )
}
复制代码

具名插槽,和作用域插槽

  • 具名插槽相当于传入一个对象,对象的key就相当于插槽的名字
  • 作用域插槽,相当于也是传入一个对象,每个对象对应key的值,传入一个函数,传入函数的原因,就是为了在子组件使用插槽的地方,把参数通过函数的参数返回回来
  • vue3中jsx 和react其实写法也是一样的,不同点就是在子组件用插槽的时候
  • vue3通过setup的第二个参数ctx.slots去获取,默认插槽是ctx.slots.default(),react的默认插槽通过props.children获取
  • vue3获取具名插槽,和作用域插槽ctx.slots.slot1(666),react中:props.children.slot1()
//react
const TestChild = () => {
  const testMainClick = (val) => {
    console.log('获取子组件传来的值', val)
  }

  return (
    <div>
      TestChild
      <MainFn testMainClick={testMainClick} >
        {
          {
            test1(val) {
              console.log('slotAAA', val)
              return <>
                <div>test1__slot</div>
                <div>{val}</div>
              </>
            }
          }
        }
      </MainFn>
    </div>
  )
}
//子组件
const MainFn = ({children, testMainClick}) => {
  const { test1 } = children

  console.log('children', children)

  const handleMain = () => {
    testMainClick('AAA')
  }

  return (
    <div>
      <button onClick={handleMain}>MainFn</button>
      <div>
        {
           test1('传递值回去')
        }
      </div>
    </div>
  )
}

复制代码

vue3中插槽的使用

/* 在vue3中用react方式的函数式组件不是响应式的 */
const TestChild = (props) => {
  console.log('props', props)
  const data = reactive({ city: '重庆' })

  const handleChild = () => {
    data.city = '成都'
    console.log('handleChild', data)
  }

  return (
    <div>
      <div>{data.city}</div>
      <div>TestChild</div>
      <button onClick={handleChild}>改变</button>
    </div>
  )
}

/* setup格式的函数式组件是响应式的 */
const TestChild2 = {
  props: {
    city: String
  },
  setup(props, ctx) {
    console.log('propssetup', props, ctx)
    const data = reactive({ city: '北京' })

    const handleChild = () => {
      data.city = '上海'
      console.log('handleChild', data)
    }

    return () => {
      return (
        <div>
          <div>{data.city}</div>
          <div>TestChild2</div>
          <button onClick={handleChild}>改变</button>
          <div>{ctx.slots.default(22)}</div>
          <div>{ctx.slots.slot1(666)}</div>
        </div>
      )
    }
  }
}

//vuejsx中子组件要向父组件传递参数,通过props.fn()
//如果是模板的方法写,需要定义emits
//方式一
<TestChild
name="AA"
    v-slots={{
        default(val) {
            return <div>TestChildAAA{val}</div>
        },
        slot1(val) {
            return <div>TestChildAAA{val}</div>
        }
}}
>
//方式二
<TestChild>
  {{
      default(val) {
            return <div>TestChildAAA{val}</div>
        },
        slot1(val) {
            return <div>TestChildAAA{val}</div>
        }
  }}  
</TestChild>
复制代码

react-dom

  • createPortal通过createPortal实现把元素插入到节点,比原生的方法性能更好
import ReactDOM from 'react-dom'
ReactDOM.createPortal(
    <div className="modal">{this.props.children}</div>,
    document.body // DOM 节点
)
复制代码

prop-types

  • prop-types实现props的类型检查
import PropTypes from 'prop-types'

复制代码

syntheticEvent

  • react的event是react自己封装的一个event,
  • react16事件绑定到document上的
  • react17事件绑定到root组件上,有利于多个react并存,例如微前端

受控组件

  • 相当于数据双向绑定
  • 在input的值,受data的改变而改变
  • onChange去控制input

非受控组件

    <input defaultValue={this.state.name} ref={this.inputRef} />
    
复制代码
  • 在上传文件时,就必须用非受控组件

setState

  • 不可变值, 什么时候修改,什么时候去加值,不要提前去加
  • 可能是异步更新
  • 可能会被合并
  • 虽然,通过数组的push这些方法,一样可以渲染成功,但是在shouldeComponentUpdate生命周期中,他们的值还是一样的,这样不能判断比较
    //nextProps,改变后的props
    // 改变后的当前state值
    //通过this.state 与nextState 和props与nextProps比较
    //如果要重新渲染,则返回true,否则返回false
    shouldComponentUpdate(nextProps, nextState) {
        console.log('nextProps', this.state)
        console.log('shouldComponentUpdate', nextProps, nextState)
        return true
    }
    this.inputRef = createRef()
    <input type='file' ref={this.inputRef} />
    console.log('ref', this.inputRef.current.files)
复制代码

immutable

  • 一般不通过深度比较,object,而是通过immutable库实现新的数据结构

原生 html

const rawHtml = '<span>富文本内容<i>斜体</i><b>加粗</b></span>'
const rawHtmlData = {
    __html: rawHtml // 注意,必须是这种格式
}
const rawHtmlElem = <div>
    <p dangerouslySetInnerHTML={rawHtmlData}></p>
    <p>{rawHtml}</p>
</div>
return rawHtmlElem
复制代码

相当于vue的nextTick

const useSyncCallback = callback => {
    const [proxyState, setProxyState] = useState({ current: false })

    useEffect(() => {
        if (proxyState.current === true) setProxyState({ current: false })
    }, [proxyState])

    useEffect(() => {
        proxyState.current && callback()
    })

    return useCallback(() => {
        setProxyState({ current: true })
    }, [proxyState])
}
const onClick = () => {
    setCount(count + 1)
    nextTick()  //执行后,获取最新的数据
}
const nextTick = useSyncCallback(() => {
    console.log('改变后', count)
})
复制代码

HOC高阶组件

import React from 'react'

// 高阶组件
const withMouse = (Component) => {
    class withMouseComponent extends React.Component {
        constructor(props) {
            super(props)
            this.state = { x: 0, y: 0 }
        }
  
        handleMouseMove = (event) => {
            this.setState({
                x: event.clientX,
                y: event.clientY
            })
        }
  
        render() {
            return (
                <div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
                    {/* 1. 透传所有 props 2. 增加 mouse 属性 */}
                    <Component {...this.props} mouse={this.state}/>
                </div>
            )
        }
    }
    return withMouseComponent
}

const App = (props) => {
    const a = props.a
    const { x, y } = props.mouse // 接收 mouse 属性
    return (
        <div style={{ height: '500px' }}>
            <h1>The mouse position is ({x}, {y})</h1>
            <p>{a}</p>
        </div>
    )
}

export default withMouse(App) // 返回高阶函数
复制代码

renderProps

import React from 'react'
import PropTypes from 'prop-types'

class Mouse extends React.Component {
    constructor(props) {
        super(props)
        this.state = { x: 0, y: 0 }
    }
  
    handleMouseMove = (event) => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }
  
    render() {
      return (
        <div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
            {/* 将当前 state 作为 props ,传递给 render (render 是一个函数组件) */}
            {this.props.render(this.state)}
        </div>
      )
    }
}
Mouse.propTypes = {
    render: PropTypes.func.isRequired // 必须接收一个 render 属性,而且是函数
}

const App = (props) => (
    <div style={{ height: '500px' }}>
        <p>{props.a}</p>
        <Mouse render={
            /* render 是一个函数组件 */
            ({ x, y }) => <h1>The mouse position is ({x}, {y})</h1>
        }/>
        
    </div>
)

/**
 * 即,定义了 Mouse 组件,只有获取 x y 的能力。
 * 至于 Mouse 组件如何渲染,App 说了算,通过 render prop 的方式告诉 Mouse 。
 */

export default App

复制代码

forwardRef

    //函数组件可以拿到ref,或者高阶组件传递时
    forwardRef((props, ref) => {})
复制代码

react-router-dom

//16.8自动注入的withRouter,所有可以直接通过props去拿到props.location,props.match.params
//也可以通过提供的hooks去拿,useParams,useLocation
//BrowserRouter h5的路由
// HashRouter hash模式
// 跳转通过useHistory   props.history.push(`/article/${res.id}`)    
// props.history.push(`/?page=1&keyword=${keyword}`)
<BrowserRouter>
    //也可以用插槽的方式
    <Route path='/testRouter' component={TestRouter}></Route>
    <Route path='/ClassRouter/:age' component={ClassRouter}></Route>
</BrowserRouter>
//exact严格匹配,完全相等时,才匹配,在嵌套路由中,二级路由可以使用,一级路由不要使用
//NavLink有一个激活的active  activeClassName
//Route有三种渲染方式,通过插槽,通过render,通过component
//一般嵌套路由使用插槽或者render,render可以让子组件拿到props,
<Route path='/Home' render={(props) =>
    <div>
        //让home拿到props
        <Home {...props} />
        <Route path='/Home/Child1' exact component={Child1} />
        <Route path='/Home/Child2' exact component={Child2} />
    </div>
}>
复制代码

react基于不可变值的设计理念,redux也要不可变值

  • hooks也要不可变值

redux

  • 需要引入三个包reduxredux-thunkreact-redux

image.png

image.png

image.png

  • redux提供注入store的一些方法,和中间件加载函数
  • react-redux主要用于获取state和dispatch,和Provide的一些组件和方法
  • redux-thunk处理异步的一个中间件,要处理异步,必须加上这个中间件 ####示例代码
import * as redux from 'redux'
import thunk from 'redux-thunk'

import * as reactRedux from 'react-redux'
import * as reduxThunk from 'redux-thunk'



console.log('redux', redux)
console.log('reactRedux', reactRedux)
console.log('reduxThunk', reduxThunk)


const { createStore, applyMiddleware } = redux

const defaultState = {
    name: '张三',
    city: '成都',
    age: 22,
    list: {
        yy: 32
    }
}

const promiseFn = (query) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('一秒后执行' + query)
        }, 1000);
    })
}

/* 同步处理 */
export const getName = (list) => {
    // 一般直接return,但是为了在返回的函数拿到值,就用异步的方法,这样可以返回一个值
    // return {
    //     type: 'NAME_ACTION',
    //     payload: list
    // }
    return (dispatch) => {
        dispatch({
            type: 'NAME_ACTION',
            payload: list
        })
        return list
    }
}

/* 异步处理 */
export const getAsyncName = (list) => {
    return (dispatch) => {
        return promiseFn(list).then(res => {
            dispatch({
                type: 'NAME_ACTION',
                payload: res
            })
            return res
        })
    }
}

const reducer = (state = defaultState, action) => {
    // console.log('测试action', action)
    if(action.type === 'NAME_ACTION') {
        // state.age = action.payload
        state.list.yy = action.payload
        state = {...state}
    }
    return state
}

const store = createStore(reducer, applyMiddleware(thunk))

import React from 'react'
import * as reactRedux from 'react-redux'
import { getName, getAsyncName } from '../../../store/index'


// console.log(';reactRedux', reactRedux, getName)

const { useDispatch, useSelector } = reactRedux



const Child1 = (props) => {
    const disptch = useDispatch()

    const age = useSelector(state => {
        // console.log('获取', state.list.yy)
        // return state.age
        return state.list.yy
    })

    const handleBtn1 = () => {
        const data = disptch(getName('同步触发了,老弟'))
        console.log('获取AAA', data)
    }

    const handleBtn2 = () => {
        disptch(getAsyncName(883263)).then(res => {
            console.log('res', res)
        })
        // console.log('获取AAA', age)
    }

    console.log('Child1的props', props)
    return <div>
        Child1
        <div>AA{age}</div>
        <button onClick={handleBtn1}>同步触发redux</button>
        <button onClick={handleBtn2}>异步触发redux</button>
    </div>
}

export default Child1

export default store

//注入在顶层store
<Provider store={store}>
    <App />
</Provider>
复制代码

react安装ts环境

npx create-react-app my-react-ts --typescript

react中使用typescript

import React from "react";

interface testProps {
    message?: string
}
// 可以通过React.FC简写
const Test: React.FunctionComponent<testProps> = (props) => {
    return <div>
        {props.message}
    </div>
}

Test.defaultProps = {
    message: '你好啊'
}

export default Test

复制代码

react中动态添加class

  • 安装npm i classnames
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改