跳至主要內容

react16.8+

Emilia Zhen大约 10 分钟react

useRef

import React, { useState, useEffect, useRef } from 'react'
const LikeButton: React.FC = () => {
  const [like, setLike] = useState(0)
  const likeRef = useRef(0)
  const didMountRef = useRef(false)
  const inputRef = useRef < HTMLInputElement > null
  useEffect(() => {
    document.title = `点击了${like}次`
  }, [like])
  useEffect(() => {
    if (didMountRef.current) {
      console.log('did mounted')
    } else {
      didMountRef.current = true
    }
  })
  const btnClick = () => {
    setLike(like + 1)
    likeRef.current = likeRef.current + 1
    if (inputRef && inputRef.current) {
      inputRef.current.focus()
    }
  }
  const consoleLike = () => {
    setTimeout(() => {
      console.log(like, 'like')
      console.log(likeRef)
    }, 2000)
  }
  return (
    <>
      <input type="text" ref={inputRef} />
      <button onClick={consoleLike}>console</button>
      <button onClick={btnClick}>👍 +{like}</button>
    </>
  )
}
export default LikeButton

react-router

版本< v6

import { HashRouter, Route, Link } from 'react-router-dom'

HashRouter是一个路由根容器,一个应用程序中,一般只需要唯一的一个HashRouter容器即可,将来所有的RouteLink都要在HashRouter中进行;HashRouter中只能有唯一的一个子元素
Link用于设置跳转路径<Link to='/home'>首页<Link>
Route 用于设置路由规则以及路由占位<Route path='/home' component={Home }></Route>路由匹配是进行模糊匹配的,可以通过Route身上的exact属性来表示当前的Route是进行精确匹配的
Redirect <Redirect from="/" to="/home"></Redirect>

switch

匹配到第一个规则后不会再往下执行其他路由匹配规则

<Switch>
  <Route path="/movie/detail/:id" component={MovieDetail} exact></Route>
  <Route path="/movie/:type/:id" component={MovieList} exact></Route>
</Switch>
路由说明
HashRouter地址栏带#,同一地址可以不同标签页打开多个页面
browserRouter地址跟正常地址一样,但是同一地址不能打开多个页面,需要使用服务端渲染解决

使用 v6

BrowserRouter HashRouter NativeRouter来包裹整个应用,通常是App组件

  • <Routes><Route>要配合使用,必须<Routes>包裹<Route>
  • <Route>相当于一个if语句,如果其路径与当前URL匹配则呈现对应的组件
  • <Route caseSensitive> 匹配时区分大小写
  • <Route>可以签到使用,可使用 useRoutes()配置路由表,但需要通过<Outlet>组件来渲染其子路由
  • <Link to="/mine" replace> 替换路由
  • <Link to="detail"> 跳转当前路由嵌套下子路由
  • <NavLink to="about" class={({isActive)) => isActive ? 'base active' : 'base'}> 高亮是默认类名active
  • <Navigate to="/" replace={true}> 只要组件被渲染就会修改路径更换视图
import { BrowserRouter } from 'react-router-dom';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>,
);

路由规则

<Routes>
  <Route path="login" element={<LoginPage />} />
  <Route path="/" element={<Layout />}>
    <Route index path="home" element={<HomePage />} />
    <Route path="mine" element={<MinePage />} />
  </Route>
</Routes>

或使用路由表

export default [
  {
    path: 'login',
    element: <LoginPage />,
  },
  {
    path: '/',
    // element: <Navigate to='/home' />,
    element: <Layout />,
    children: [
      {
        element: <HomePage />,
        index: true,
      },
      {
        path: 'mine',
        element: <MinePage />,
      },
    ],
  },
  {
    path: '*',
    element: <NotFound />,
  },
]

function App() {
  const routerElement = useRoutes(routers)
  return <div className={style.App}>{routerElement}</div>
}

const Layout: React.FC = () => {
  return (
    <Content>
      <Outlet />
    </Content>
  )
}

useParams

// {
//   path: 'mine/:id',
//   element: <MinePage />,
// },

import { useParams } from 'react-router-dom'
const MinePage: React.FC = () => {
  const { id, type } = useParams()
  return (
    <div>
      {id},{type}
    </div>
  )
}

useSearchParams

// /mine?id=1&&type=2

import { useSearchParams } from 'react-router-dom'
const MinePage: React.FC = () => {
  const [search, setSearch] = useSearchParams()
  return (
    <div>
      {search.get('id')},{search.get('type')}
    </div>
  )
}

传 state

;<div>
  <Link to="mine" state={{ name: 'xx', age: 18, id: 9 }}>
    TO Mine
  </Link>
</div>

import { useLocation } from 'react-router-dom'

const MinePage: React.FC = () => {
  const {
    state: { id, age, name },
  } = useLocation()
  return (
    <div>
      {id},{age},{name}
    </div>
  )
}

编程式路由导航

import { useNavigate } from 'react-router-dom'
const GlobalHeader: React.FC = () => {
  const navigate = useNavigate()
  const goBack = () => {
    navigate(-1)
  }
  const goForward = () => {
    navigate(-1)
  }
  return (
    <div>
      <Button onClick={goBack}> back </Button>
      <Button onClick={goForward}> forward </Button>
    </div>
  )
}

其他 Hooks

useInRouterContext 返回Boolean,值为当前组件是否在路由器包裹
useNavigationType 返回当前的导航类型 pop/push/replacePOP指在浏览器中直接打开了这个路由组件(刷新页面)
useOutlet 呈现当前组件中渲染的嵌套组件,嵌套路由还未挂载时返回null
useResolvedPath 参数传入路径,解析该路径中的path/serch/hash

生命周期

组件的生命周期是指组件被创建到挂载到页面中运行起来,再到组件不用时卸载的过程。只有类组件才有生命周期(类组件 实例化,函数组件 不需要实例化)

挂载阶段

钩子函数触发时机作用
constructor创建组件时,最先执行,初始化时只执行一次初始化 stater;创建 ref;用 Bind 解决 this 指向问题等
render每次组件渲染都会触发 渲染 UI(不能在里面调用 setState)
componentDidMount组件挂载(完成 DOM 渲染)后执行,初始化时执行一次 发送网络请求;DOM 操作

更新阶段

钩子函数触发时机作用
render每次组件渲染都会触发渲染 UI(与挂载阶段是同一个 render)
componentDidUpdate组件更新后(DOM 操作完毕)DOM 操作,可以获取到更新后的 DOM 内容,不要直接调用 setState

卸载阶段

钩子函数触发时机作用
componentWillUnmount组件卸载(从页面中消失)执行清理工作(比如:清理定时器等)

componentWillReaciveProps(nextProps)当组件接收的属性值发生变化会被触发;nextProps为最新属性值 shouldComponentUpdate(nextProps,nextState){return true}当组件的props或者state中的数据发生变化时会被触发,返回true才会继续执行后续生命周期;当return true时会重新执行render函数;nextPropsnextState接收组件最新的属性值和状态值 componentDidUpdate(prevProps,prevState)render函数已经重新被执行会触发此函数

使用 css 模块化

行内样式需要使用对象进行描述,以前中划线连接变成驼峰命名法

return (
  <div style={{ border: '1px solid gray', margin: '10px 0', padding: '0 10px' }}>
    <p>姓名:{props.name}</p>
    <p>年龄:{props.age}</p>
  </div>
)

可以在webpack.config.js中为css-loader启用模块化,后面加上参数modules表示启用模块化css

{ test: /\.css$/, use: ['style-loader', 'css-loader?modules&localIdentName=[name]_[local]-[hash-5]'] },

在组件中引入css并使用样式

import React from 'react'
import itemStyle from './HelloItem.css'
export default function HelloItem(props) {
  return (
    <div className={itemStyle.box}>
      <p>姓名:{props.name}</p>
      <p>年龄:{props.age}</p>
    </div>
  )
}

模块化内部的全局样式

.box {
  border: 1px solid gray;
}
:global(.title) {
  color: skyblue;
}

hooks

React体系里组件分为类组件和函数组件
经过多年的实战,函数组件是一个更加匹配React的设计理念UI=f(data),也更有利于逻辑拆分与重用的组件表达形式,而先前的函数组件是不可以有自己的状态的,为了能让函数可以拥有自己的状态,所以从React v16.8开始,Hooks应运而生
有了hooks之后,为了兼容老版本,class类组件并没有被移除,两者都可以使用
有了hooks之后,不能再把函数成无状态组件了,因为hooks为函数组件提供了状态
hooks只能在函数组件中使用

解决了

  1. 组件的逻辑复用 在hooks出现之前,react先后尝试了mixins混入、HOC高阶组件、render-props等模式,但是都有各自的问题,比如mixin的数据来源不清晰,高阶组件的嵌套问题等
  2. class组件自身的问题 class组件大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期、this指向问题等

useState

为函数组件提供状态

import React, { useState, useEffect } from 'react'
const LikeButton: React.FC = () => {
  useEffect(() => {
    document.title = `点击了${like}次`
  }, [like])
  const [like, setLike] = useState(0)
  return <button onClick={() => setLike(like + 1)}>👍 +{like}</button>
}
export default LikeButton

组件第一次渲染:从头开始执行该组件中的代码逻辑;调用useState,将传入的参数作为状态初始值;渲染组件,此时获取到的状态值为0
组件第二次渲染:点击按钮调用setLike修改状态,因为状态发生改变,所以该组件会重新渲染;组件重新渲染时会再次执行该组件中的代码逻辑;再次调用useState,此时React内部会拿到最新的状态值,而非初始值;再次渲染组件,此时获取到的状态值为1
useState的初始值只会在组件第一次渲染时生效
修改状态时,一定要使用新的状态替换旧的状态,不能直接修改旧的状态,尤其是引用类型
useState不能嵌套在if/for/其他函数中,react按照hooks的调用顺序识别每一个hook

useEffect

react函数组件提供副作用处理

  1. 不添加依赖项 组件首次渲染执行一次,不管哪个状态更改引起组件更新时都会重新执行
useEffect(() => {
  console.log('副作用执行了')
})
  1. 添加空数组 组件只在首次渲染时执行一次
useEffect(() => {
  console.log('副作用执行了')
}, [])
  1. 添加特定依赖项 副作用函数在首次渲染时执行,在依赖项发生变化时重新执行
function App() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('zs')
  useEffect(() => {
    console.log('副作用执行了')
  }, [count])
  return (
    <>
      <button
        onClick={() => {
          setCount(count + 1)
        }}>
        {count}
      </button>
      <button
        onClick={() => {
          setName('cp')
        }}>
        {name}
      </button>
    </>
  )
}

清理副作用

可以在函数末尾return一个新的函数,在新函数中编写清理逻辑
组件卸载时自动执行;组件更新时,下一个useEffect副作用函数执行之前自动执行

const App = () => {
  const [count, setCount] = useState(0)
  useEffect(() => {
    const timerId = setInterval(() => {
      setCount(count + 1)
    }, 1000)
    return () => {
      // 用来清理副作用的事情
      clearInterval(timerId)
    }
  }, [count])
  return <div>{count}</div>
}

自定义 Hook

import { useState, useEffect } from 'react'
const useMousePosition = () => {
  const [position, setPosition] = useState({
    x: 0,
    y: 0,
  })
  useEffect(() => {
    const updateMouse = (e: MouseEvent) => {
      setPosition({
        x: e.clientX,
        y: e.clientY,
      })
    }
    document.addEventListener('click', updateMouse)
    return () => {
      document.removeEventListener('click', updateMouse)
    }
  }, [])
  return position
}
export default useMousePosition

Hooks 进阶

useState回调函数参数
如果初始state需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的state,此函数只在初始渲染时被调用

function Counter(props) {
  const [count, setCount] = useState(() => {
    return props.count
  })
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  )
}

useEffect发送网络请求

useEffect(() => {
  async function fetchData() {
    const res = await axios.get('http://xxxx.net/v1/channels')
    console.log(res)
  }
}, [])

useRef

在函数组件中获取真实的dom元素对象或者是组件对象

import { useEffect, useRef } from 'react'
function App() {
  const h1Ref = useRef(null)
  useEffect(() => {
    console.log(h1Ref)
  }, [])
  return (
    <div>
      <h1 ref={h1Ref}>this is h1</h1>
    </div>
  )
}

获取类组件实例,函数组件没有实例

class Foo extends React.Component {
  sayHi = () => {
    console.log('say hi');
  };
  render() {
    return <div>Foo</div>;
  }
}
export default Foo;
// app.jsx
import { useEffect, useRef } from 'react';
import Foo from './Foo';
function App() {
  const h1Foo = useRef(null);
  useEffect(() => {
    console.log(h1Foo);
  }, []);
  return (
    <div>
      {' '}
      <Foo ref={h1Foo} />
    </div>
  );
}
export default App;

useContext

import { createContext, useContext } from 'react'
// 创建Context对象
const Context = createContext()
function Foo() {
  return (
    <div>
      Foo <Bar />
    </div>
  )
}
function Bar() {
  // 底层组件通过useContext函数获取数据
  const name = useContext(Context)
  return <div>Bar {name}</div>
}
function App() {
  return (
    // 顶层组件通过Provider 提供数据
    <Context.Provider value={'this is name'}>
      <div>
        <Foo />
      </div>
    </Context.Provider>
  )
}
export default App

Mobx

MobX 通过 makeObservable 方法来构造响应式对象,传入的对象属性会通过 Proxy 代理,与 Vue 类似,在 6.0 版本之前使用的是 Object.defineProperty API

npm i mobx  mobx-react-lite

react 使用

  1. 定义模块 1
import { makeAutoObservable } from 'mobx'
class TaskStore {
  taskList = []
  constructor() {
    makeAutoObservable(this)
  }
  addTask() {
    this.taskList.push('vue', 'react')
  }
}
const task = new TaskStore()
export default task
  1. 定义模块 2
import { makeAutoObservable } from 'mobx'
class CounterStore {
  count = 0
  list = [1, 2, 3, 4, 5, 6]
  constructor() {
    makeAutoObservable(this)
  }
  addCount = () => {
    this.count++
  }
  changeList = () => {
    this.list.push(7, 8, 9)
  }
  get filterList() {
    return this.list.filter((item) => item > 4)
  }
}
const counter = new CounterStore()
export default counter
  1. 组合模块统一导出
import React from 'react'
import counter from './counterStore'
import task from './taskStore'
class RootStore {
  constructor() {
    this.counterStore = counter
    this.taskStore = task
  }
}
const rootStore = new RootStore()
// context机制的数据查找链  Provider如果找不到 就找createContext方法执行时传入的参数
const context = React.createContext(rootStore)
const useStore = () => React.useContext(context)
export { useStore }
  1. 使用
import { observer } from 'mobx-react-lite'
// 导入方法
import { useStore } from './store'
function App() {
  // 得到store
  const store = useStore()
  return (
    <div className="App">
      <button onClick={() => store.counterStore.addCount()}>{store.counterStore.count}</button>
    </div>
  )
}
// 包裹组件让视图响应数据变化
export default observer(App)

监听对象变更

const store = makeAutoObservable({
  count: 0,
  setCount(count) {
    this.count = count
  },
  increment() {
    this.count++
  },
  decrement() {
    this.count--
  },
})
// store 发生修改立即调用 effect
autorun(() => {
  $count.innerText = `${store.count}`
})
// 第一个方法的返回值修改后才会调用后面的 effect
reaction(
  // 表示 store.count 修改后才会调用
  () => store.count,
  // 第一个参数为当前值,第二个参数为修改前的值
  // 有点类似与 Vue 中的 watch
  (value, prevValue) => {
    console.log('diff', value - prevValue)
  }
)
// 第一个方法的返回值为真,立即调用后面的 effect
when(
  () => store.count > 10,
  () => {
    console.log(store.count)
  }
)(
  // when 方法还能返回一个 promise
  async function () {
    await when(() => store.count > 10)
    console.log('store.count > 10')
  }
)()