创建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
useCallck与useMemo极其类似,可以说是一模一样,唯一不同的是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是用来连接React和Redux的桥梁
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;
},
}
})