React 复习

感谢满神,小满zs yyds!

1. 基本

  1/ 差值语句 类似 Vue {{}}  React 使用 {}

     可以渲染 字符串 数字 数组(普通类型) 元素 三元运算符 API调用

  2/ 差值语句支持对象怎么弄 [{ name: 1},{ name: 2 }] 需要序列化 JSN.stringify()

  3/ 事件如何添加 驼峰 onClick  eg: onClick={ fn }

    传参函数 eg: onClick = { () => { fn(123, e) } }

  4/ 想支持泛型函数 因为他会把<T> 理解成了一个元素 <div> 标签 纠正泛型<T,>

  5/ 如何去绑定属性 id class 等等 class比较特殊 必须叫 className

  6/ 如何去绑定多个class 用字符串拼接   className={`${cls} aaa bbb ccc`}

  7/ 绑定style { color: 'red' }   style={ styles }

  8/ 添加html代码片段v-html  dangerouslySetInnerHTML={{ __html: html }}

  9/ 如何遍历数组 v-for

let arr: number[] = [1,2,3,4,5,6,7,8,9]

<div>
        { arr.map( v=> {
          return <div key={v}>{v}</div>
        })}
 </div>

2. Hooks

  1/ useState  类似Vue 的 ref reactive

    useState 是一个异步方法 这样设计的原因是为了性能优化 并且天然自带防抖 使用回调处理 setData((prev) => prev + 1)

    基本数据类型

// count 参数默认值 setCount 参数的Set方法 使用该方法改变参数值
  const [str, setStr] = useState('点击我')
 // const [str, setStr] = useState<string>('点击我')  一般不用传类型 靠推导
  const updateStr = () => {
    setStr('点击我1')
  }

<div onClick={ updateStr }>{ str }</div>

   

    复杂数据类型

// 复杂数据类型
  let [arr, setArr] = useState(['类型一','类型二','类型三','类型四'])
  // 复杂数据类型不能直接修改原数组  
  const updateArr = () => {
    setArr([...arr, '类型五'])
  }
  // 使用函数
  let [myObj, setObj] = useState(() => {
    // 处理逻辑 一定要返回值
    // 这个函数只会在初始化执行一次
    const date = new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate()
    return {
      date,
      name: '贲风',
      age: 30
    }
  })

  const updateObj = (obj) => {
    setObj(Object.assign({}, obj, {age: 22}))
  }

<div onClick= { () => { updateObj(myObj) } }>改变{ myObj.name }的信息</div>
      <div>{ myObj.date } - { myObj.name } - { myObj.age }</div>

 

  2. useReducer

    与useState 都是永远管理组件状态的 不同在于 useReducer 是集中式的管理状态

    useState 一般用于基本数据类型 useReducer 一般用于复杂数据类型

    在React 中 所有的hook 都必须在组件的最顶层调用

 

  基本用法

<div>
        <button onClick={ () => setNumber({ type: 'add' }) }>+1</button>
        <button onClick={ () => setNumber({ type: 'sub' }) }>-1</button>
        <div>{myNumber.count}</div>
</div>

// 默认值
  const initState = {
    count: -1,
  };

  type State = typeof initState;

  // 初始化函数
  const initFn = (state: State) => {
    return { count: Math.abs(state.count) };
  };
  // 处理函数reducer
  const reducer = (state: State,action: { type: 'add' | 'sub' }) => {
    switch (action.type) {
      case "add":
        return { count: state.count + 1 }
      case "sub":
        return { count: state.count - 1 }
      default:
        return state
    }
  };

// 三个参数 处理函数 默认值 初始化函数
  const [myNumber, setNumber] = useReducer(reducer, initState, initFn);

  实际例子 购物车功能

const initData = [{
    name: '数据A',
    price: 100,
    count: 1,
    id: 1,
    isEdit: false
  },{
    name: '数据B',
    price: 100,
    count: 1,
    id: 2,
    isEdit: false
  },{
    name: '数据C',
    price: 100,
    count: 1,
    id: 3,
    isEdit: false
  }]

  type Data = typeof initData

  // 处理函数
  const cartRecuder = (state: Data, action: { type: 'add' | 'sub' | 'update' | 'edit' | 'del', id: number, newName?: string  }) => { 'add' 
    console.log('state', state)
    const item = state.find(item => item.id === action.id)!
    switch (action.type) {
      case 'add': 
        item.count++
        return [...state]
      case 'sub':
        item.count--
        return [...state]
      case 'update':
        item.name = action.newName!
        return [...state]
      case 'edit':
        item.isEdit = !item.isEdit
        console.log('state', state)
        return [...state]
      case 'del':
        return state.filter(item => item.id !== action.id)
      default: 
        return state
    }
  }

  const initCart = (state: Data) => {
    return state
  }

  const [cartArr, setCartArr] = useReducer(cartRecuder, initData, initCart)


//html
<div>
        <h1>购物车</h1>
        <table cellSpacing={0} cellPadding={0} width={800} border= {1}>
          <thead>
            <tr>
              <th>商品</th>
              <th>单价</th>
              <th>数量</th>
              <th>总价</th>
              <th>操作</th>
            </tr>
          </thead>
          <tbody>
            {
              cartArr.map(item => {
                return <tr key={item.id}>
                  <td>
                  { item.isEdit ? <input onChange={ (e) => { setCartArr({type: 'update', id: item.id, newName: e.target.value}) }} onBlur={ () => { setCartArr({type: 'edit', id: item.id}) }} value={item.name}></input> : item.name }
                  </td>
                  <td>{ item.price }</td>
                  <td>
                    <button onClick={ () => setCartArr({type: 'add', id: item.id}) }>+</button>
                    { item.count }
                    <button>-</button>
                  </td>
                  <td>{ item.price * item.count }</td>
                  <td>
                    <button onClick = { () => { setCartArr({type: 'edit', id: item.id })}}>修改</button>
                    <button onClick = { () => { setCartArr({type: 'del', id: item.id })}}>删除</button>
                  </td>
                </tr>
              })
            }
          </tbody>
          <tfoot>
            <tr>
              <td colSpan={5}>总价: {cartArr.reduce((a,b) => a + b.price * b.count, 0)}</td>
            </tr>
          </tfoot>
        </table>
      </div>

  3. useSyncExternalStore

    场景:

      订阅外部数据 redux, Zustand

      订阅浏览器API(online, storage, location)等

      抽离逻辑,编写自定义hooks

      服务端渲染支持

const res = useSyncEnternalStore(subscribe, getSnapshot, getServerSnapshot!)

· subscribe  
    用来订阅数据源变化,接受一个回调函数,在数据源更新时调用该回调函数

· getSnapshot 
    获取当前数据圆的快照(当前状态)

· getServerSnapshot?
    在服务器渲染时用来获取数据源的快照
// 实际例子
// hook.ts
import { useSyncExternalStore } from "react"

export const useStorage = (key: string, initialValue: any) => {
    // 订阅数据变化
    const subscribe = (callback: () => void) => {
        window.addEventListener('storage', callback)
        return () => {
            // 返回取消订阅
            window.removeEventListener('storage', callback)
        }
    }
    // 快照
    const getSnapshot = () => {
        return JSON.parse(localStorage.getItem(key)!) || initialValue
    }

    const res = useSyncExternalStore(subscribe,getSnapshot)
    // 
    const updateStorage = (value: any) => {
        console.log('value', value)
        localStorage.setItem(key, JSON.stringify(value))
        // 手动触发storage事件 否则 数据不会更新
        window.dispatchEvent(new StorageEvent('storage'))
    }
    return [res, updateStorage]
}

// 主页面
import { useStorage } from "./hooks/useStorage";
const [myCount, setCount] = useStorage("count", 1);
<div>
        <button onClick={() => setCount(myCount + 1)}>+</button>
        count is {myCount}
        <button onClick={() => setCount(myCount - 1)}>-</button>
      </div>

  【注意事项】

      // getSnapshot 返回值不同于上一次 React会重新渲染组件 这就是为什么返回一个不同的值 会进入一个无限循环,并报错 即不要返回新的引用类型
// 如果返回引用

//正常
getSnapshot = () => {
      return myStore.todos
}    


//
getSnapshot = () => {
      if(myStore.todos !== lastTodos) {
            //只有在todos真的发生改变时,才更新快照
            lastSnapshot = { todos: myStore.todos.slice() }
             lastTodis = myStore.todos               
      }
      return lastSnapshot
}    

  4. useTransition

    帮助你在不阻塞UI的情况下更新状态的React Hook

    用法:  const [isPending, stratTransition] = useTransition()

      不需要任何参数

      isPending 布尔值 告诉你是否存在待处理的transition

      stratTransition 函数 你可以以此方法将状态更新标记为 transition  函数内不可以使用异步函数

const [isPending, startTransition] = useTransition()

const handleChange = (e:React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value
    setValue(value)
    fetch('/api/list?keyword=' + value).then(res=> 
      res.json()
    ).then(res => {
      console.log('res', res.list)
      // 加载慢时 使用这个可以好一些
      startTransition(() => {
        setList(res.list)
      })
    })
  }

<List dataSource={list} renderItem={ (item) => 
          <List.Item>
            <List.Item.Meta title={item.name} description={item.address} ></List.Item.Meta>
          </List.Item>}>
        </List>        

 

  5. useDeferredValue 

    用于延迟某些状态的更新,知道主渲染任务完成

    常见问题: 与useTransition 的区别

    · useTransition 主要关注点是【状态的过渡】, 它允许开发者控制某个更新的延迟更新,还提供了过渡标识,让开发者能够天津爱过渡反馈

    · useDeferredValue 主要关注点是 【单个值】 的延迟更新,它允许你把特定状态的更新标记为低优先级

const deferredQuery = useDeferredValue(val)

const findItem = () => {
    console.log(deferredQuery, '----', val)
    return list.filter((item) => item.name.toString().includes(deferredQuery))
  }


 <Flex>
          <Input value={val} onChange={handleChange}></Input>
        </Flex>
        <List dataSource={findItem()} renderItem={ (item) => 
          <List.Item>
            <List.Item.Meta title={item.name} description={item.address} ></List.Item.Meta>
          </List.Item>}>
        </List>

 

  6. useEffect

    React 中用于处理副作用的钩子 并且 useEffect 还在这里充当生命周期函数, 使用 componentDidMount、 componentDidUpdate、 componentWillUnmount来处理这些生命周期事件

    【面试题】 什么是纯函数 什么是副作用函数

      纯函数 不改变外部数据状态 操作可预测

      副作用函数 会改变外部状态或者依赖  操作不可预测  高耦合的

    基本用法 useEffect(setup, dependencies?)   没有返回值

      · setup  处理函数 返回一个清理函数 组件挂载时执行setup,一来项更新时先执行cleanup再执行setup,组件卸载时执行cleanup

      · dependencies(可选)  必须以数组形式编写 不传则每次渲染都执行Effect  空数组时只触发一次 适合初始化 详情数据

     例子:

const [btnStatus, setBtnStatus] = useState(true)

// 组件
const Child = (props:any) => {
  useEffect(() => {
    console.log('子组件')
    return () => {
      console.log('子组件卸载')
    }
  }, [props.name])
  return <div>child</div>
}


<button onClick={() => setBtnStatus(!btnStatus) }>显示/隐藏</button>
{ btnStatus && <Child name={name}></Child> }

  6 - useEffectEvent

    用于在useEffect 中使用 响应式数据

    由于useEffectEvent 中的逻辑是非响应式的,而且能一直“看见”最新的 props 和 state。你可以用起包裹响应式数据在useEffect中使用,而不需要监听他们

  基本语法: const myEvent = useEffectEvent(() =>{ setState(xxxx) }) 其实与普通函数并无区别

  tip:  还有一种useState 达成类似的方案  使用 setValue(c => c + 1);  而不是 setValue(number + 1); 这种也可以达到同样的规避检测的效果

  7. useLayoutEffect

    用于浏览器重新绘制屏幕之前触发 与 useEffect类似

  基本语法:同上

  【面试题】 useEffect 和 useLayoutEffect 有什么区别

    执行时机

      useLayoutEffect  浏览器完成布局和绘制【之前】触发  useEffect 浏览器完成布局和绘制【之后】触发

    执行方式

      useLayoutEffect 同步执行  useEffect 异步执行

    Dom渲染

      useLayoutEffect 阻塞DOM渲染  useEffect 不阻塞DOM渲染

  例子

useLayoutEffect(() => {
    const container = document.querySelector('#container') as HTMLDivElement;
    const top = window.location.search.split('=')[1]
    container.scrollTop = Number(top) || 0
  })

  const scrollHandler = (e:React.UIEvent<HTMLDivElement>) => {
    const scrollTop = e.currentTarget.scrollTop
    console.log(scrollTop)
    window.history.replaceState({}, '', `?scrollTop=${scrollTop}`)
  }
  return (
    <>
      <div onScroll={ scrollHandler } id="container" style={{ height: '500px', overflow: 'auto'}}>
        {
          Array.from({ length: 100}).map((item,index) => {
            return <div key={index}>{index}</div>
          })
        }
      </div>
    </>
  )

 

  8. useRef

    用途  (貌似和Vue ref 差不多 😂)

      1. 获取DOM

      2. 存储数据

    基本用法

      const value = useRef(defaultValue)    value.current 才是真正的数据

    【面试题】  Vue ref 和 React useRef 的区别

      1. 属性名不同 一个是 .value  一个是 .current

      2. Vue 是响应式的 React 并不是

      3.  来自DeepSeek 老师的总结

        image

    【注意事项】

      1. 组件重新渲染时, useRef的值不会被重新渲染

      2. ref只是一个不同的javascript对象 并非响应式

      3. useRef 的值不能作为 useEffect 等其他hooks的依赖项,因为他并不是一个响应式状态

      4. useRef 不能直接获取子组件的实例,需要使用forwardRef

    应用场景 计时器 DOM元素等

      

  9. useImpertiveHandle

    用途

      可以在父组件中使用子组件的方法或属性, 类似 defineExpose

       ref 父组件传递的ref组件

useImperativeHandle(ref, () => {
   return {
       //暴漏给父组件的方法或属性     
   }      
},[deps])

    例子 React18

interface ChildRef {
  name: string
  count: number
  addCount: () => void
  subCount: () => void
}

// 子组件
const Child = React.forwardRef<ChildRef>((props, ref) => {
  const [count,setCount] = useState(0)
  useImperativeHandle(ref, () => {
    return {
      name: 'ChildName',
      count,
      addCount: () => setCount(count + 1),
      subCount: () => setCount(count - 1)
    }
  })
  return <div>
    <h3>子组件</h3>
    <div>count: {count}</div>
    <button onClick={() => setCount(count + 1)}>+1</button>
  </div>
})

function App() {
  // raect18版本左右 useRef 不是必传的
  // raect19版本左右 useRef 是必传的

  const childRef = useRef<ChildRef>(null)

  const getChildInfo = () => {
    console.log(childRef.current)
  }

  return (
    <>
     <div>父组件</div>
     <button onClick={ getChildInfo }>获取子组件信息</button>
     <button onClick={ () => childRef.current?.addCount() }>操作子组件count+1</button>
     <button onClick={ () => childRef.current?.subCount() }>操作子组件count-1</button>
     <Child ref={ childRef } />
    </>
  )
}

  React19

interface ChildRef {
  name: string
  count: number
  addCount: () => void
  subCount: () => void
}

// 子组件
const Child = ({ref}: {ref:React.Ref<ChildRef>}) => {
  const [count,setCount] = useState(0)
  useImperativeHandle(ref, () => {
    return {
      name: 'ChildName',
      count,
      addCount: () => setCount(count + 1),
      subCount: () => setCount(count - 1)
    }
  })
  return <div>
    <h3>子组件</h3>
    <div>count: {count}</div>
    <button onClick={() => setCount(count + 1)}>+1</button>
  </div>
}

function App() {
  // raect18版本左右 useRef 不是必传的
  // raect19版本左右 useRef 是必传的

  const childRef = useRef<ChildRef>(null)

  const getChildInfo = () => {
    console.log(childRef.current)
  }

  return (
    <>
     <div>父组件</div>
     <button onClick={ getChildInfo }>获取子组件信息</button>
     <button onClick={ () => childRef.current?.addCount() }>操作子组件count+1</button>
     <button onClick={ () => childRef.current?.subCount() }>操作子组件count-1</button>
     <Child ref={ childRef } />
    </>
  )
}

 

  10.  useContect  (感觉类似Vue 的 Provide Inject)

    用途: 进行数据的组件传递  可以跨层级 即 父组件 => 孙组件 或更深等级传递

      使用 createContext 创建全局上下文  const ThemeContext = React.createContext({})   // 创建context 并传一些初始值

      然后 const value = useContext(上下文)                       // 在需要使用context 的组件中 useContext  传入刚才穿件的context

      然后使用 ThemeContext.Provider 组件包裹需要使用的组件 value 绑定需要传的值    // 在需要提供数据的组件内 使用 Context 包裹需要使用Context的组件 React19的例子中不需要.Provider   

      

interface IThemeContext {
  theme: string
  setTheme: (theme: string) =>void
}

// 1.创建全局的上下文 这里创建完成后 ThemeContext 就变成了一个组件
const ThemeContext = React.createContext({} as IThemeContext)
const Child = () => {
  return <div>
    <div>
      Child
    </div>
  </div>
}

const Parent = () => {
  const theme = useContext(ThemeContext)
  console.log('theme', theme)
  return <div>
    <div>
      Parent -- { theme.theme }
    </div>
    <Child />
  </div>
}

function App() {
  const [theme,setTheme] = useState('light')

  return (
    <div>
      <ThemeContext.Provider value={{ theme, setTheme}}>
        <Parent />
      </ThemeContext.Provider>
    </div>
  )
}

  可多层嵌套  会使用最内部层的数据

 <ThemeContext.Provider value={{ theme, setTheme}}>
        <ThemeContext.Provider value={{ theme:'aaa', setTheme}}>
          <Parent />
        </ThemeContext.Provider>
      </ThemeContext.Provider>

  【注意】React18中采用.Provider  React19中去掉了Provider

  

  11. useMemo

    用途:用于性能优化,主要用于避免在每次渲染时执行重复的计算和对象重建,通过记忆上一次的计算结果,仅当依赖项变化时才会重新计算(懂了,Vue Computed)

    用法:useMemo(() => count, [count])

      · 回调函数 返回需要缓存的值

      · 依赖项 Array 依赖项发生变化时 回调函数会重新执行 (执行机制与useEffectl类似)

    useMemo 返回的是普通值 并不是一个函数

  11.2 Memo API  感觉类似keepalive

    例子

// 子组件
const UserCard = React.memo((props: {user: User}) => {
  const { user } = props
  console.log('渲染')
  return <div className="user-card">
    <p>{ user.name }</p>
    <p>{ user.age }</p>
    <p>{ user.phone }</p>
  </div>
})

function App() {
  const [user,setUser] = useState({
    name: '贲风',
    age: 95,
    phone: '89757'
  })
  const [input, setInput] = useState('')
  const changeUser = () => {
    setUser({
      ...user,
      name:'张三'
    })
  }
  return (
    <>
      <button onClick={ changeUser }>更改User</button>
      <input type="text" value={ input } onChange={ (e) => { setInput(e.target.value) }  } />
      <UserCard user={ user }></UserCard>
    </>
  )
}

  12. useCallback

    用途:用于优化性能 返回一个记忆化的回调函数 可以减少不必要的重新渲染,也就是说他是用于缓存组件中的函数,避免函数逇重复创建的

    用法:const myCallback = useCallback(() => { doSomething(a,b)},[a,b])

    参数:

      · casllback: 回调函数

      ` deps:依赖项数组 不传则只触发一次 传了根据依赖项执行 跟 useEffext一样

    【面试题】 与useMemo 的区别

      返回值不同 memo 返回的是值 callback 返回的是函数

  13. useDebugValue  (后面学)

    用途:专门为开发者调试自定义Hook而设计的 React Hook,它允许你在React开发者工具为自定义Hook添加自定义的调试值。

    用法: const debugValue = useDebugValue(value)

      · value 要在React DevTools 中显示的值

      · formatter?: (可选)格式化函数

  14. useId (React18+ 新增)(后面细学 感觉不是很常用)

    用途: 用于生成稳定的唯一标识符,主要用于SSR 场景下ID不一致的问题,或需要为组件生成唯一ID的场景

    用法:const id = useID()

      返回值 :r0:  多次调用递增

3. 组件

  跟Vue 差不多 没太想好怎么整理...

4. 受控组件和非受控组件

  受控组件

    一般是useState 

  非受控组件

    一般是useRef(通过操作Dom)

5. 异步组件 Suspense

  骨架屏使用

6. API createPortal

  用法:createPortal(children,domNode,key?)

    children   要渲染的组件

    domNode  要渲染到的DOM位置

    key?     可选,用于唯一标识要渲染的组件

  应用场景

    · 弹窗

    · 下拉框

    · 全局提示

    · 全局遮罩

    · 全局Loading

. tailwindcss 使用

  React Router

  【路由模式】

  大部分与Vue-router 类似 有几种路由模式 

    createBrowserRouter
      类似  history模式
    createHashRouter
      类似  hash模式
    createMemoryRouter
      所有路由表都存在内存当中 地址栏无变化
      使用场景:非浏览器环境 react Native Electron
      缺点: 刷新回到主页
    createStaticRouter
      专门为SSR而生的路由模式 或者需要SEO优化
  【传参方式】
    query
      获取方式一
      使用 useSearchParams 获取 类似于 useState  
        const [searchParams, setSearchParams ] = useSearchParams();
      获取值 使用 searchParams.get(键名)  获取
      获取方式二
      使用 useLocation 
        const location = useLocation()   
    params
      在路由中定义参数  /path/:id
      to="/path/3"
      取值 使用 useParams  const params = useParams()
    state
      该方式是无痕的
      与前两者不同 使用 state= {} 来进行传参
      接受使用 useLocation() 中的 state 接收
      页面不可分享 数据只存在于当前对话中
  【懒加载 lazy】
    一般配合 useNavigation
      navigation.state
        idle     空闲状态
        submitting  提交状态
        loading    加载状态
    同时 lazy 的路由 在打包时还会拆包
import { createBrowserRouter } from "react-router-dom";
import Layout from "../layout";
import Home from '../pages/Home'

const sleep = (ms:number) => new Promise(resolve => setTimeout(resolve, ms))
// import About from '../pages/About'
const router = createBrowserRouter([
  {
    path: "/",
    Component: Layout,
    children: [
      {
        path: "home",
        Component: Home
      },{
        path: "about",
        // Component: About
        lazy: async () => {
          await sleep(2000)
          const about = await import('../pages/About')
          return {
            Component: about.default
          }
        }
      }
    ]
  },
]);

 

  【路由操作】

    loader

      以往操作  渲染组件 =>  获取数据(Fetch)   =>  渲染视图

      现在操作  loader获取数据(Fetch) =>  获取数据(useLoaderData)  =>  渲染组件

    action 表单提交

       页面中使用 useSubmit 与事件相绑定

const Home = () => {
    //  state={{name: '张三', age: 30}}
    const pageData = useLoaderData()
    console.log('pageData', pageData)
    const submit = useSubmit()
    const onFinish = (values: any) => {
        
        submit(values, { 
            method: 'post',
            // 默认是formData
            // encType: 'application/json'
        })
    };

    return <Card>
        <h1>Home</h1>
        <Form onFinish={onFinish}>
            <Form.Item label="名字" name="name">
                <Input></Input>
            </Form.Item>
            <Form.Item label="年龄" name="age">
                <Input></Input>
            </Form.Item>
            <Form.Item >
                <Button type="primary" htmlType="submit">提交</Button>
            </Form.Item>
        </Form>

        {
            pageData.data.map((item:any, index:number) => {
                return <div key={index}>{item.name} -- { item.age}</div>
            })
        }
    </Card>
}

    在路由处使用action 进行后续处理

{
        path: "home",
        Component: Home,
        loader: async () => {
          await sleep(2000)
          return {
            data,
            success: true
          }
        },
        action: async ({request}) => {
          const formData = await request.formData() // 默认通过 formData 提交数据
          const json = Object.fromEntries(formData)
          console.log('json', json)
          sleep(2000)
          return {
            success: true,
            message: '提交成功'
          }
        }
      }

 

posted on 2025-03-25 22:53  贲风  阅读(32)  评论(0)    收藏  举报