学习react

131 阅读8分钟

创建react项目

npx create-react-app app --template typescript
yarn create react-app react-pro --template typescript

如果你不想使用TpyeScript配置可以删除 --后面的参数进行创建

如果你想在ts中使用import styles from './App.module.css',你需要配置*d.ts文件配置如下

//*d.ts文件 只包含类型声明,不包含逻辑
//*d.ts文件 不会被编译,也不会被webpack打包
declare module "*.css" {
  const css: {[key: string]: string }
  export default css
}

什么是hooks

把某个目标结果钩到某个可能会变化的数据源或者事件源上,那么当被钩到的数据或者事件发生变化时,产生这个目标结果的代码会重新执行,产生更新后的结果。 hooks一律使用use前缀命名:useXxx

什么是纯函数

不依赖于 且 不改变 它作用域之外的变量状态的函数

什么是副作用

  • 与药物的副作用类似:减肥药(拉肚子),头孢(过敏),泰诺(头痛)
  • 副作用(函数中修改了全局变量,修改传入参数,使用axios请求,修改dom元素,console打印都属于副作用)与纯函数相反,指一个函数处理了与返回值无关的事情

有状态和无状态组件的区别

  • 无状态组件(展示组件,函数式组件):就是一个函数没有props,没有生命周期, 就是一个简单的视图函数
  • 标准使用模式此函数继承了React里面的组件和props,可以使用生命周期可以再里面写业务逻辑, 可以再里面做任何事情

HOC是什么

不管原来的组件多复杂,都会在原来的组件一层层的嵌套,达到复用的目的,加深了组件的复杂度,有可能造成回调地域Dom结构

hooks钩子介绍

useState

const [state, setState] = useState(initialState);

返回一个 state,以及更新 state 的函数

函数式更新

如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 `setState`。该函数将接收先前的 state,并返回一个更新后的值。下面的计数器组件示例展示了 `setState` 的用法
function App() {
     const [count, setCount] = useState(0);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(0)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

惰性初始 state

initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用

useEffect

该 Hook 接收一个包含命令式、且可能有副作用代码的函数

useEffect(() =>{
    fetch('https://www.**.com/users')
    .then((response) =>response.json())
    .then((data) =>setRobotGallery(data))
  },[])
  // []空数组表示只执行一次
  // 如果不传第二个参数它的副作用会在每次渲染都会执行,每次使用api调用接口
  // 都是执行 setRobotGallery更新组件状态,robotGallery更新,render从新渲染
  // 副作用再次执行,陷入无限死循环
  
  如何在useEffect中使用async/await
   useEffect( () =>{
    const fectchData = async () =>{
      const response = await fetch('https://www.**.com/users')
      const data = await response.json()
      setRobotGallery(data)
    }
    fectchData()
  },[])

useCallback

useCallckuseMemo极其类似,可以说是一模一样,唯一不同的是useMemo返回的是函数运行的结果,而useCallback返回的是函数

注意:这个函数是父组件传递子组件的一个函数,防止做无关的刷新,其次,这个组件必须配合memo,否则不但不会提升性能,还有可能降低性能

//父组件
const App = (props) =>{
  const [count,setCount] =useState<number>(0)
  const [count1,setCount1] =useState<number>(0)
  const [count2,setCount2] =useState<number>(0)
  return(
  <div className={styles.app}>
      <div>
        count:{count}
        <button onClick={() => setCount(count+1)}>点击</button>
        <br></br>
        <br></br>
        <Child click={()=>setCount1(count1+1)}></Child>
        <br></br>
        <br></br>
        <Child click={useCallback(()=>setCount2(count2+1),[count2])}></Child>
      </div>
  </div)
}
//子组件
import React from "react";
const Child: React.FC<any> = (props) =>{
  console.log("children")
  return <span>{Math.random()}</span>
}
//当你不使用React.memo,count 只是在父组件使用的,改变它使我们子组件又重新渲染了
//原因是count 改变后会引起父组件的重新渲染,而每次重新渲染都会生成一个新函数,所以子组件 props 在浅比较的时候就会认为 props 改变了,引起子组件不必要的渲染
export default React.memo(Child)
// export default Child

useMemo

当一个父组件中调用了一个子组件的时候,父组件的 state 发生变化,会导致父组件更新,而子组件虽然没有发生改变,但也会进行更新

简单的理解下,当一个页面内容非常复杂,模块非常多的时候,函数式组件会从头更新到尾,只要一处改变,所有的模块都会进行刷新,这种情况显然是没有必要的

只要父组件的状态更新,无论有没有对自组件进行操作,子组件都会进行更新,useMemo就是为了防止这点而出现的

const App = (props) =>{
  const [num,setNum] =useState(0)
  const [value,setValue] =useState("")
  //我们改变了 `value` 的值后也触发了 `computedNum` 的重新计算,这显然是没必要的
  所有可以使用useMemo进行优化
  const computedNum = () =>{
    console.log('computedNum')
    return num+10
  }
  const computedNum = useMemo(() =>{
    console.log('computedNum')
    return num+10
  },[num])
  return (
      <div>
        {/* 不使用useMemo的情况 */}
        {/* computedNum:{computedNum()} */}
        {/* 使用useMemo的情况 */}
        computedNum:{computedNum}
        <br/>
        <br/>
        <input
          type="text"
          value={value}
          onInput={(e:any)=>{
            setValue(e.target.value)
          }}
        ></input>
      </div>
  )
}

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);
第一个参数:reducer函数,没错就是我们上一篇文章介绍的。第二个参数:初始化的state。返回值为最新的state和dispatch函数(用来触发reducer函数,计算对应的state)。按照官方的说法:对于复杂的state操作逻辑,嵌套的state的对象,
// 第一个参数:应用的初始化
const initialState = {count: 0};

// 第二个参数:state的reducer处理函数
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  // 返回值:最新的state和dispatch函数
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
     // useReducer会根据dispatch的action,返回最终的state,并触发rerender
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

可以尝试改造login状态

useContext

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定

//appState.tsx
import React, { useState } from 'react'

interface AppStateValue {
  username: string;
  shoppingCart: { items: { id: number; name: string }[] };
}
const deffalueContextValue :AppStateValue = {
  username :"你好",
  shoppingCart:{
    items:[]
  }
}
export const appContext =React.createContext(deffalueContextValue)
export const  appSetStateContext = React.createContext<
React.Dispatch<React.SetStateAction<AppStateValue>>|undefined
>(undefined)
export const AppStateProvider = (props) =>{
  const [state,setState] =useState(deffalueContextValue)
  return (
    <appContext.Provider value={state}>
      <appSetStateContext.Provider value={setState}>
        {props.children}
      </appSetStateContext.Provider>
    </appContext.Provider>)
}

//index.tsx
import {AppStateProvider} from './AppState'
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <AppStateProvider>
      <App />
    </AppStateProvider>
  </React.StrictMode>
);

//addPage.tsx
使用deffalueContextValue数据 可以通过useContext
import React,{useContext} from "react";
import {appContext,appSetStateContext} from '../AppState'

直接获取到value对象就是deffalueContextValue
const value =useContext(appContext)

//通过useContext获取setState函数,对deffalueContextValue进行操作
const setState =useContext(appSetStateContext)
    const addToCart =(id,name) =>{
      if(setState){
        setState((state) =>{
          return {
            ...state,
            shoppingCart:{
              items:[...state.shoppingCart.items,{id,name}]
            }
          }
        })
      }
    }

useRef

返回引用的对象,在整个组件的生命周期中保持不变

useLayoutEffect

处理副作用,在所有的dom元素变更后,同步调用,读取dom布局,并同步触发从新渲染

路由(6.3版本,与之前的版本有点不一样)

路由是什么

当浏览器的url产生变化时,浏览器页面相应的发生改变

SPA是什么

  • js,css,html打包为一个超级大的文件每一次新丢给浏览器
  • js劫持浏览器路由,生成虚拟路由来动态渲染页面dom元素
  • 符合前后台分离的趋势,服务器不负责ui输出,而专注于数据支持
  • 同时支持桌面App,手机App,网站App
//路由配置
function App() {
  return (
    <div className={styles.App}>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<HomePage/>}/>
          <Route path="/singIn" element={<SingInPage/>}/>
          <Route path="/register" element={<RegisterPage/>}/>
           {/* 在组件中使用 useParams 拿到参数 */}
          <Route path="/detail/:touristRouteId" element={<DetailPage/>}/>
          {/* <Route path="/search/:keywords?" element={<SearchPage/>}/> */}
        </Routes> 
      </BrowserRouter>
    </div>
  ); 
}
如果你想跳转路由使用 
header.tsx
如果路由方式是<Route path="/detail/:touristRouteId" element={<DetailPage/>}/>
获取参数可以可以再DetailPage页面使用
 let params = useParams() 来获取touristRouteId的值
 
如果路由方式是<Route path="/singIn" element={<SingInPage/>}/>
使用 let navigate = useNavigate() 跳转
  <Button onClick={()=>{navigate('/singIn',{state:{a:1,b:2,c:3}})}}>登录</Button>
  获取参数 const params = useLocation()
  
 <Button><Link to={`/register?id='111'`}>登出</Link></Button> 
  获取参数 
 const [ getParams ,setParam] = useSearchParams() 
  console.log(getParams.getAll('id'))

redux

Redux 是一个使用叫做 “action” 的事件来管理和更新应用状态的模式和工具库** 它以集中式 Store 的方式对整个应用中使用的状态进行集中管理,确保状态只能以可预测的方式更新

react-redux

  • react-redux 提供 useDispatch和useSelector方法
  • React-Redux是用来连接ReactRedux的桥梁

redux-thunk

  • thunk 可以返回一个函数,而不一定是js对象
  • 在一个thunk action中可以完成一些列连续的action操作
  • 并且可以处理异步逻辑 公共配置
//store.ts
import { createStore, combineReducers, applyMiddleware } from 'redux';
import languageReducer from "./language/languageReducer";
import recommendProductsReducer from "./recommendProducts/recommendProductsReducer";
import thunk from "redux-thunk";
import { actionLog } from "./middlewares/actionLog";
const rootReducer = combineReducers({
    language: languageReducer,
})
const store = createStore(rootReducer, applyMiddleware(thunk, actionLog));
export type RootState = ReturnType<typeof store.getState>
export default store;

//hooks.ts
import {
  useSelector as useReduxSelector,
  TypedUseSelectorHook,
} from "react-redux";
import { RootState } from "./store";

//封装useSelector,为了少写一个RootState
export const useSelector: TypedUseSelectorHook<RootState> = useReduxSelector;

//actionLog.ts
import { Middleware } from "redux";
export const actionLog: Middleware = (store) => (next) => (action) => {
  console.log("state 当前", store.getState());
  console.log("fire action ", action);
  next(action);
  console.log("state 更新", store.getState());
}

//index.tsx
import{Provider} from 'react-redux'
import store  from './redux/store'
ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
)

业务配置

//redux文件 language文件 languageAactions.ts
export const CHANGE_LANGUAGE = "change_language";
export const ADD_LANGUAGE = "add_language";

interface ChangeLanguageAction {
  type: typeof CHANGE_LANGUAGE;
  payload: "zh" | "en";
}
interface AddLanguageAction {
  type: typeof ADD_LANGUAGE;
  payload: { name: string; code: string };
}
export type LanguageActionTypes = ChangeLanguageAction | AddLanguageAction;

//action  changeLanguageActionCreator
export const changeLanguageActionCreator = (
  languageCode: "zh" | "en"
): ChangeLanguageAction => {
  return {
    type: CHANGE_LANGUAGE,
    payload: languageCode,
  };
};

//action addLanguageActionCreator
export const addLanguageActionCreator = (
  name: string,
  code: string
): AddLanguageAction => {
  return {
    type: ADD_LANGUAGE,
    payload: { name, code },
  };
};

import { CHANGE_LANGUAGE, ADD_LANGUAGE, LanguageActionTypes } from "./languageActions";

export interface LanguageState {
  language: "en" | "zh";
  languageList: { name: string; code: string }[];
}

const defaultState: LanguageState = {
  language: "zh",
  languageList: [
    { name: "中文", code: "zh" },
    { name: "English", code: "en" },
  ],
};

//languageReducer.ts
export default (state = defaultState, action: LanguageActionTypes) => {
  switch (action.type) {
    case CHANGE_LANGUAGE:
      return { ...state, language: action.payload };
    case ADD_LANGUAGE:
      return {
        ...state,
        languageList: [...state.languageList, action.payload],
      };
    default:
      return state;
  }
};
import {
  LanguageActionTypes,
  addLanguageActionCreator,
  changeLanguageActionCreator,
} from "../../redux/language/languageActions";
import { useSelector } from "../../redux/hooks";
import { useDispatch } from "react-redux";
//获取状态数据
const language = useSelector((state) => state.language.language);

发送action
 const dispatch = useDispatch();
 dispatch(changeLanguageActionCreator('你好'));

@reduxjs/toolkit

比使用 redux react-redux redux-thunk代码量更少

redux-toolkit.js.org/introductio…

使用 redux react-redux 与使用 @reduxjs/toolkit store.ts文件不一样的地方 
import { combineReducers, configureStore } from "@reduxjs/toolkit";
const rootReducer = combineReducers({
    user:userSlice.reducer,
})
const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), actionLog],
  devTools: true,
});
export type RootState = ReturnType<typeof store.getState>
export default store;

//productDetail文件夹 slice.ts
import {createSlice,PayloadAction,createAsyncThunk} from "@reduxjs/toolkit"
import axios from 'axios'

interface UserState {
  loading: boolean;
  error: string | null;
  token: userInfo
}
interface userInfo {
  username: string | null
  password: string | null
}

const initialState: UserState = {
  loading: false,
  error: null,
  token: {
    username:'',
    password:'',
  },
};

export const singnIn =createAsyncThunk<string,any>(
  'user/singnIn',
  async (paramaters: { 
    email:string
    password: string
  }, thunkAPI)=>{
    const { data } =await axios.get(`/data/mocklogin.json`)
    console.log(paramaters,data.data.token)
    return data.data.token
  }
)

export const userSlice = createSlice({
  name:'userSlice',
  initialState,
  reducers: {
    //同步
    logOut: (state) =>{
      state.token= {username:'',password:'',}
      state.loading =false
      state.error =null
    }
  },
  //异步Reducers extraReducers
  extraReducers: {
    [singnIn.pending.type]: (state) => {
      state.loading = true;
    },
    [singnIn.fulfilled.type]: (state, action) => {
      state.token = action.payload;
      state.loading = false;
      state.error = null;
    },
    [singnIn.rejected.type]: (state, action: PayloadAction<string | null>) => {
      state.loading = false;
      state.error = action.payload;
    },
  }
})