React扩展

13 阅读4分钟

CSS

styled-components: yarn add styled-components

// props透传
// attrs的使用
// 传入state作为props属性
const StyledInput = styled.input.attrs({
  type: 'password',
  placeholder:'请输入',
  bgColor:'#000'
})`
  color: ${props => props.color};
  background-color: ${props => props.bgColor};
`

// 继承
const StyledInput2 = styled(StyledInput)`
  margin-top: 20px;
  padding: 10px;
`

动画

npm install react-transition-group --save

<CSSTransition
    nodeRef={this.nodeRef}
    in={this.state.show}
    timeout={500}
    classNames="fade"
    unmountOnExit
    appear
>
    <h1 ref={this.nodeRef}>CSS Transition</h1>
</CSSTransition>

.fade-enter, .fade-appear{
    opacity: 0;
}

.fade-enter-active, .fade-appear-active{
    opacity: 1;
    transition: opacity 500ms ease-in-out;
}

.fade-enter-done, .fade-appear-done{
    opacity: 1;
}
.fade-exit{
    opacity: 1;
}


.fade-exit-active{
    opacity: 0;
    transition: opacity 500ms ease-in-out;
}

.fade-exit-done{
    opacity: 0;
}

Redux

// index.js
import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(reducer)
store.subscribe(() => {
  console.log(store.getState())
})
store.dispatch(addAction(1))
store.dispatch(subAction(10))

// action.js
export const addAction= (num) => ({
  type: ADD_NUMBER,
  num
})
export const subAction= (num) => ({
  type: SUB_NUMBER,
  num
})

// reducer.js
const defaultlState = {
  count: 0,
}
function reducer(state = defaultlState, action) {
  switch (action.type) {
    case ADD_NUMBER:
      return {
        ...state,
        count: state.count + action.num,
      } 
    case SUB_NUMBER:
      return {
        ...state,
        count: state.count - action.num,
      } 
    default:
      return state
  }
}


// 加入connect
// index.js
import { createStore, applyMiddleware } from 'redux'
**import { thunk } from 'redux-thunk'**
import reducer from './reducer'

const storeEnhancer = applyMiddleware(thunk)

const store = createStore(reducer, storeEnhancer)    

export default store

// reducer.js
import { createStore, applyMiddleware } from 'redux'
import { thunk } from 'redux-thunk'
import reducer from './reducer'

const storeEnhancer = applyMiddleware(thunk)

const store = createStore(reducer, storeEnhancer)    

export default store

// actionCreator.js
import { ADD_NUMBER, SUB_NUMBER } from './constans'

export const addAction= (num) => ({
  type: ADD_NUMBER,
  num
})

export const subAction= (num) => ({
  type: SUB_NUMBER,
  num
})

**export const getBannerList = (dispatch) => (
  console.log('getBannerList')
)**

// connect.js
import { PureComponent } from "react";
import store from '../store'

export function connect(mapStateToProps, mapDispatchToProps) {
  return function (Component) {
    return class ConnectedComponent extends PureComponent {
      constructor(props) {
        super(props)
        this.state = {
            storeState: mapStateToProps(store.getState()),
        }
      }
      componentDidMount() {
        this.unsubscribe = store.subscribe(() => {
          this.setState({
            storeState: mapStateToProps(store.getState()),
          })
        })
      }
      componentWillUnmount() {
        this.unsubscribe()
      }
      render() {
        return (
          <Component
            {...this.props}
            {...this.state.storeState}
            {...mapDispatchToProps(store.dispatch)}
          />
        );
      }
    };
  };
}
// home.js
import { connect } from '../utils/connect'
import { addAction, subAction, getBannerList } from '../store/actionCreator'

const mapStateToProps = (state) => ({
  count: state.count
})

const mapDispatchToProps = (dispatch) => ({
  addAction: (num) => dispatch(addAction(num)),
  subAction: (num) => dispatch(subAction(num)),
  getBannerList: () => dispatch(getBannerList)
})

function Home2(props) {
    return (
        <>
            <div>
                <h1>HOME</h1>
                <h2>当前计数:{props.count}</h2>
            </div>
            <button onClick={() => props.addAction(1)}>+1</button>
            <button onClick={() => props.subAction(1)}>-1</button>
            <button onClick={() => props.getBannerList()}>获取轮播图</button>
        </>
    ) 
}

export default connect(mapStateToProps, mapDispatchToProps)(Home2)

router

npm install react-router-dom
BrowserRouter: history模式
HashRouter: hash模式

<BrowserRouter>
    <Link to="/a">首页</Link>
    <Link to="/about">关于</Link>
    <Routes>
        <Route path="/a" element={<Home />} />
        <Route path="/about" element={<About />} />
    </Routes>
</BrowserRouter>

function Routes() {
  return useRoutes([    
      {
        path: '/home',
        element: <Home />
      },
      {
        path: '/about',
        element: <About />,
        children: [
          {
            path: 'history',
            element: <AboutHistory />
          },
          {
            path: 'school',
            element: <AboutSchool />
          },
          {
            path: 'company',
            element: <AboutCompany />
          }
        ]
      }
])}

Hooks

class组件比函数式组件的优势(没有hooks的时候)
    1.class组件可以定义自己的state,用来保存组件自己内部的状态
      函数式组件不可以,因为函数每次调用都会产生新的临时变量
    2.class组件有自己的生命周期,比如在componentDidMounted中发送网络请求,
      并且在生命周期函数只会执行一次
      在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求
    3.class组件可以在状态改变时只会重新执行rendr函数以及生命周期componentDidUpdate
      函数式组件在重新渲染时,整个函数都会被执行
      
useState
本身是一个函数,来自react包
参数和返回值
    1.参数:给创建出来的状态一个默认值
    2.返回值:数组,元素1:当前state的状态值,元素2是更新状态的函数
只能在函数最外层调用Hook,不要在循环,条件判断或者子函数中使用
只能在react的函数组件中调用hook,不能在其他js函数中调用

useCallback
// memo 用于包裹函数组件,使其在 props 未发生变化时跳过重新渲染,从而提升性能。
// 父组件重新渲染,子组件也会重新渲染,但是如果子组件被 memo 包裹,且 props 未发生变化,那么子组件就不会重新渲染。
// HYBButton1的increment函数会重新创建,所以memo包裹的props发生变化,会重新渲染
// HYBButton2的increment函数不会重新创建,所以memo包裹的props没有发生变化,不会重新渲染
const HYBButton = memo(({title, increment}) => {
  console.log('重新渲染', title)
  return (
    <button onClick={increment}>
      +1
    </button>
  )
})

export default function Callback() {
  const [count, setCount] = useState(0)
  const [isShow, setIsShow] = useState(true)
  const handleClick1 = () => {
    setCount(count + 1)
    console.log(111)
  }

  const handleClick2 = useCallback(() => {
    setCount(count + 1)
    console.log(222)
  },[count])

  return (
    <div>
      <p>当前计数: {count}</p>
      <p>当前显示状态: {isShow ? '显示' : '隐藏'}</p>
      <HYBButton title="增加1" increment={handleClick1}  />
      <HYBButton title="增加2" increment={handleClick2} />
      <button onClick={() => setIsShow(!isShow)}>显示/隐藏</button>
    </div>
  )
}

useContext
App.js
export const UserContext = createContext()
<React.StrictMode>
    <UserContext.Provider value={{name: '张三', age: 18}}>
      <MemoDemo />
    </UserContext.Provider>
</React.StrictMode>

index.js
import { UserContext } from '../index'
export default function User() {
  const user = useContext(UserContext)
  return (
    <div>
      <p>当前用户: {user.name}</p>
      <p>当前用户年龄: {user.age}</p>
    </div>
  )
}

useMemo
import { useState,useMemo,memo } from 'react';
function calcNum(count){
    let total = 0
    for(let i = 0; i < count; i++) {
        total += i
        console.log('total',total)
    }
    return total
}

const HYUser = memo(function HYUser(props) {
  const {name, age} = props
  console.log('HYUser', name, age)
  return(
    <div>
      <p>当前用户: {name}</p>
      <p>当前年龄: {age}</p>
    </div>
  )
})

export default function MemoDemo() {
  const [count, setCount] = useState(0)
  const [isShow, setIsShow] = useState(true)
//   let total = 0
//   for(let i = 0; i < count; i++) {
//     // 切换show每次都会执行for循环
//     total += i
//   }
  const total = useMemo(() => calcNum(count), [count])
  const info = useMemo(() => ({name: '张三', age: 18}), []) // 依赖项为空数组,只在组件挂载时执行一次
//const info = {name: '张三', age: 18} 每次重新渲染都创建一个新对象,会导致HYUser组件重新渲染

  return(
    <div>
      <p>当前计数: {count}</p>
      <p>当前显示状态: {isShow ? '显示' : '隐藏'}</p>
      <p>当前total: {total}</p>
      <HYUser info={info} />
      <button onClick={() => setCount(count + 1)}>增加</button>
      <button onClick={() => setIsShow(!isShow)}>切换显示状态</button>
    </div>
  )
}
- useMemo :缓存值 ,用于优化复杂计算
- useCallback :缓存函数 ,用于优化子组件渲染
- useCallback 是 useMemo 的特例 : useCallback(fn, deps) 等价于 useMemo(() => fn, deps)

useReducer
import { useReducer } from 'react'
function reducer(state, action) {
  switch(action.type) {
    case 'increment':
      return {counter: state.counter + action.payload}
    case 'decrement':
      return {counter: state.counter - action.payload}
    default:
      return state
  }
}
export default function User() {
  const [state, dispatch] = useReducer(reducer, {counter: 0})
  return(
    <div>
      <p>当前状态: {state.counter}</p>
      <button onClick={() => dispatch({type: 'increment', payload: 1})}>增加</button>
      <button onClick={() => dispatch({type: 'decrement', payload: 1})}>减少</button>
    </div>
  )
}

useLayoutEffect
useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新
useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新
如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect