首先根据typescript的特性,需要对redux中的接口和数据类型进行interface
与type
定义
// 数据类型
export interface todoItem{
id:string,
content:string,
done:boolean
}
export interface todos{
[index:number]:todoItem
}
// action.type
export const add_Todo="addTodo";
export const set_Done="setDone";
// action 接口类型
interface addTodoAction{
type:typeof add_Todo
content:string
}
interface setDoneAction{
type:typeof set_Done
id:string
done:boolean
}
// 联合类型表明可能的action
export type TodoActionTypes=addTodoAction|setDoneAction
依据types类型定义,需要对action进行函数定义,使得后面可以在组件中直接注入reducer
import {add_Todo,set_Done,TodoActionTypes} from './types'
// action 定义
export function addTodo(content:string) :TodoActionTypes{
return{
type:add_Todo,
content
}
}
export function setDone(id:string,done:boolean):TodoActionTypes{
return{
type:set_Done,
id,
done
}
}
依据types类型与接口定义,编写reducer函数,实现redux功能
import {todoItem,TodoActionTypes,delete_Todo,add_Todo,set_Done,set_Content,toggle_Done} from './types'
function guid(){
var s4=()=>{
return (((1+Math.random())*0x10000|0).toString(16).substring(1))
};
//return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`
return s4()
}
const newTodo=(content:string)=>({
done:false,
id:guid(),
content:(content||"").trim()
})
const initialState:todoItem[]=JSON.parse(localStorage.getItem("todos")||"[]");
export function TodoReducer(
state=initialState,
action:TodoActionTypes
) :todoItem[]{
switch(action.type){
case delete_Todo:
return state.filter(todo=>todo.id!==action.id);
case add_Todo:
return [...state,newTodo(action.content)];
case set_Done:
return state.map(todo=>todo.id===action.id?{...todo,done:action.done}:todo);
case set_Content:
return state.map(todo=>todo.id===action.id?{...todo,content:action.content}:todo);
case toggle_Done:
return state.map(todo=>todo.id===action.id?{...todo,done:!todo.done}:todo);
default:
return state
}
}
根据业务需求,编写中间件,在每次reducer执行前后完成特定操作
import {TodoActionTypes} from './types'
import { todoItem } from './types';
import {postTodo} from '../../axios/index'
export type MiddlewareFunction = (store: any) => (next: any) => (action: TodoActionTypes) => any;
const setLocal=(todos:todoItem[])=>{
localStorage.setItem('todos',JSON.stringify(todos));
}
const post=async (todos:[])=>{
const res= await postTodo(todos);
console.log(res)
}
export const localSet :MiddlewareFunction= store => next => action => {
let result = next(action)
setLocal(store.getState().TodoReducer)
return result
}
export const postTodos:MiddlewareFunction=store=>next=>action=>{
console.log(store.getState().TodoReducer)
let result = next(action);
post(store.getState().TodoReducer)
return result
}
进行reducers合并,并且暴露store
并且,为了后面能够将数据state注入到组件中,需要暴露Reducer的typescript类型
import {combineReducers,createStore,applyMiddleware} from 'redux'
import {localSet,postTodos} from './todos/middleware'
import {TodoReducer} from './todos/reducers'
// 合并reducers
const rootReducer=combineReducers({
TodoReducer
})
export const store=createStore(rootReducer,
// 中间件注入
applyMiddleware(localSet));
export type AppState=ReturnType<typeof rootReducer>
首先,需要在react主入口中注入store,使得在整体环境中可以获取redux
import {store} from './reducers/index'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App></App>
</Provider>,
document.getElementById('root')
);
接着,就是在组件中使用,理论上,现在可以通过props.store.dipatch()
以及props.store.getState()
使用redux了,但是对于复杂的业务场景,需要把state和reducer直接注入到组件中进行使用,如下:
import { connect } from 'react-redux'
import { bindActionCreators, Dispatch } from 'redux';
import {AppState} from '../../reducers/index';
import {todoItem} from '../../reducers/todos/types';
import {deleteTodo,addTodo,setDone,setContent,toggleDone} from '../../reducers/todos/actions'
import * as actions from '../../reducers/todos/actions'
// 将数据state注入到组件中
const mapStateToProps=(state:AppState)=>({
todos:state.TodoReducer
})
// 根据actions定义将dispatch方法注入到组件中
const mapDispatchToProps=(dispatch:Dispatch)=>({
todosAction:bindActionCreators(actions,dispatch)
})
// 定义组件props类型
interface Props{
todosAction:{
deleteTodo:typeof deleteTodo,
addTodo:typeof addTodo,
setDone:typeof setDone,
setContent:typeof setContent,
toggleDone:typeof toggleDone,
}
todos:todoItem[]
}
const TodoList:React.FC<Props> = (props)=>{
// 从props中获取state和dispatch方法
let reduxTodos=props.todos;
let {addTodo,deleteTodo,setDone,setContent,toggleDone}=props.todosAction;
}
// 实现注入
export default connect(mapStateToProps,mapDispatchToProps)(TodoList)
axios实现对服务端的请求
import axios from 'axios'
import {AxiosInstance} from 'axios'
import { todoItem } from '../reducers/todos/types';
const axiosTodo:AxiosInstance=axios.create({
baseURL:"http://localhost:3001"
});
axiosTodo.interceptors.request.use(req=>{
console.log('http request '+req.method+' '+req.url);
return req
})
export function getTodo(params:object={}){
const url='/todo';
return axiosTodo({
method:'GET',
url,
params
}).then(res=>{
return res.data?.todos||[]
}).catch(err=>{
console.log(err);
return err
})
}
export function postTodo(data:todoItem[],params:object={}){
const url='/todo';
return axiosTodo({
method:'POST',
url,
params,
data:data
}).then(res=>{
return res.data
}).catch(err=>{
console.log(err);
return err
})
}
axios实现对mock数据的请求
import axios from 'axios'
// 环境判断后,进行mock的引入
if(process.env.NODE_ENV=='development'){
require('../mock/product');
}
const axiosMock=axios.create({
baseURL:'/localtest',
})
axiosMock.interceptors.request.use((req)=>{
console.log('catch data from mock :'+req.method+' '+ req.url);
return req
})
export function getproduct(count=16){
const url='/products';
return new Promise((resolve,reject)=>{
return axiosMock.get(url).then(res=>{
resolve(res.data)
}).catch(err=>{
reject(err)
})
})
}
mock的定义形式如下:
import Mock from 'mockjs'
// 设置延时
Mock.setup({
timeout:500
})
// 请求形式与返回数据的定义
export default Mock.mock('/localtest/products','get',{
"arr|12":[
{
"id|+1":1,
"name|1-2":'商品名',
"brand|+1":['Apple','Beats','Sonos','B&O','Bose','DJI'],
"color|+1":['白色','金色','红色','蓝色','黑色'],
"sales|+1":588,
"cost|+1":1288,
"stock|+1":2
}
]
})
对于一些常用的hooks,给出一些使用样例:
对于异步请求,可以将useEffect
看作是mounted
和unmounted
import React, {useCallback,useMemo,useEffect} from 'react'
let [todos,setTodos]=useState([])
useEffect(() => {
const get=async ()=>{
const todos= await getTodo();
console.log(todos)
setTodos(todos)
};
get();
},[setTodos])
如果对于useState([])
中的数据需要进行定义,使得后面业务代码可以正确判断类型,可以进行封装:
import {useState} from 'react'
import {todoItem} from '../reducers/todos/types';
export default function useTodoState(defaultValue:todoItem[]){
const [todos,setTodos]=useState(defaultValue);
return [todos,setTodos] as const
}
对于一些依赖一些数据进行计算的数据,可以使用useMemo
,指定依赖的数据项
const visibleTodos = useMemo(
() =>
filter
? todos.filter(todo =>
filter === "active" ? !todo.done : todo.done
)
: todos,
[todos, filter]
);
const ifAnyDone=useMemo(()=>todos.some(todo=>todo.done),[todos]);
const allDone=useMemo(()=>visibleTodos.every(todo=>todo.done),[visibleTodos]);
对于需要注入到元素中的方法,可以通过useCallback
进行封装,依赖一些数据项决定是否重设;也可以对一些逻辑进行分离
const onToggleAll=useCallback(()=>{
visibleTodos.forEach(todo=>setDone(todo.id,!allDone))
},[visibleTodos,allDone]);
const onClearCompleted=useCallback(()=>{
todos.forEach(todo=>{
if(todo.done){
deleteTodo(todo.id)
}
})
},[todos]);
在业务开发中,可以对一些hooks进行二次封装,以完成我们对逻辑的分离,减少冗余代码,暴露特定的接口
比如,对input输入的onChange,对应到useState设置的值,可以进行简单的封装,暴露onChange接口
import {useCallback,useState} from "react"
export default function useInput(deafultValue:string=""){
const [value,setValue]=useState(deafultValue||"");
const onChange=useCallback(value=>{
setValue(value)
},[]);
return [value,onChange,setValue] as const
}
对于需要对输入的特定情况进行监听,对应到useCallback需要返回一个函数,可以进行判定的封装:
import { useCallback } from 'react';
export interface callback{
(e:object):void
}
export default function useOnEnter(callback:callback,inputs:string[]|object[]){
return useCallback(event=>{
if(event.key==="Enter"){
event.preventDefault();
callback(event);
}
},inputs);
}
使用:
const onAddTodo=useOnEnter(()=>{
if(newValue){
addTodo(newValue);
setValue('')
}
},[newValue])
根据业务逻辑,对于一些非hooks情景,依据闭包的形式进行封装:
interface onClick{
(event:object,...rest:[]):void
}
interface onDoubleClick{
(event:object,...rest:[]):void
}
export default function useDoubleClick(onClick:onClick|null,onDoubleClick:onDoubleClick|null) {
let clicks:number[]=[];
let timeout:number;
return (event:object,...rest:[])=>{
clicks.push(new Date().getTime());
clearTimeout(timeout);
timeout=window.setTimeout(()=>{
if(clicks.length>1&&(clicks[clicks.length-1]-clicks[clicks.length-2]<250)){
if(onDoubleClick){
onDoubleClick(event,...rest);
}
}else if(onClick){
onClick(event,...rest);
}
clicks=[];
},250)
}
}
react生态强大,以上只是简单的探索,作为个人记录使用