- 准备 React 开发环境
- react 实现功能,发现 State 数据分散在各个组件。在某些场景下不易维护,
- 使用 redux 重构,实现集中化管理 State。发现组件和 store 通信麻烦,存在重复样板代码
- 使用 react-redux 重构,优化代码
- 使用 hooks 重构,进一步优化代码,异步处理还放在组件中
- 使用 redux-thunk 重构,支持异步任务处理,如网络请求等。出现配置麻烦、处理可变数据、代码逻辑分散等问题
- 使用 @reduxjs/toolkit 重构
一、准备环境
1、使用脚手架创建工程
npx create-react-app react_typescript --template typescript
2、安装 react-router-dom
yarn add react-router-dom
3、 安装 antd
yarn add antd
修改 src/index.css,在文件顶部引入 antd 的样式。
@import '~antd/dist/antd.css';
4、安装 prettier
yarn add prettier -D
<!--.prettierrc.js-->
module.exports = {
trailingComma: "es5", //尾随逗号
tabWidth: 4,
semi: true, //句尾分号
singleQuote: false, //不允许单引号
};
5、安装 axios
yarn add axios
二、react 实现功能
1、帖子相关
- 帖子列表:id、title、content、user、date
- 帖子操作:反应表情
- 新增帖子
- 编辑帖子
- 帖子详情
2、用户相关
- 用户列表:id、name
3、示例代码
以帖子列表页面为例,流程:
- 获取数据
- 更新state
- 触发重新渲染
- 页面更新
/**
* 帖子列表
* */
const PostListPage = ({ history }: Props) => {
const [data, setData] = useState<Post_dao[]>([]);
useEffect(() => {
ajax.get("/data/postList.json")
.then((res: AxiosResponse<Post_dao[]>) => {
console.log(res);
const { data } = res;
if (data) {
setData(data);
}
})
.catch((error) => {
message.error(error.message);
});
}, []);
return (
<div className={styles.main}>
<Typography.Title level={3}>帖子列表</Typography.Title>
<List
dataSource={data}
renderItem={(item) => (
<List.Item>
<Post
key={item.id}
data={item}
/>
</List.Item>
)}
grid={{
gutter: 16,
column: 1,
}}
/>
</div>
);
};
export default PostListPage;
三、 redux 重构
redux 引入3个概念:store、reducer、action。流程:
- 组件订阅 store
- 组件--> store--> dispatch(action)--> reducer处理--> store变更 --> store 通知组件变更--> 组件更新 State
- 触发组件重新渲染
- 页面更新
<!--action-->
export const PostListType = "postList";
export const postList_action = (data: Post_dao[]) => {
return {
type: PostListType,
data,
};
};
<!--reducer-->
const posts = (state = [], action: AnyAction) => {
switch (action.type) {
case PostListType: {
return [...action.data];
}
default: {
return state;
}
}
};
export default posts;
<!--组件-->
const PostListPage = ({ history, store }: Props) => {
const [data, setData] = useState<Post_dao[]>([]);
useEffect(() => {
const updateData = () => {
//数据绑定
const allState = store.getState();
const { posts } = allState;
if (posts) {
if (posts.length <= 0) {
ajax.get("/data/postList.json")
.then((res: AxiosResponse<Post_dao[]>) => {
const { data } = res;
if (data) {
store.dispatch(postList_action(data));
}
})
.catch((error) => {
message.error(error.message);
});
} else {
setData(posts);
}
}
};
const unsubscribe = store.subscribe(updateData);
updateData();
return () => {
unsubscribe();
};
}, [store]);
const onSave = (post: any) => {
store.dispatch(postNew_action(post));
};
};
export default PostListPage;
每个用到 store 的组件都要处理store数据映射问题:
- 订阅和取消订阅
- 从 allState 中读取到 需要的数据
四、react-redux 重构
react-redux 优化了 store 和 组件绑定的体验。
- 通过
connect()
在 store 和 组件之间添加一个处理层函数mapStateToProps
,把数据通过props传递给组件,优化订阅问题 - 把从 allstate 读取数据逻辑,转移到
mapStateToProps
,使得组件更纯粹
4.1 实现思路
然后流程:
- store 通过 connect()绑定组件
- 组件--> store--> dispatch(action)--> reducer处理--> store变更 --> mapStateToProps-->更新props--> 组件更新 State
- 触发组件重新渲染
- 页面更新
const PostListPage = (props: Props) => {
const { history, posts, dispatch } = props;
useEffect(() => {
const updateData = () => {
if (posts) {
if (posts.length <= 0) {
ajax.get("/data/postList.json")
.then((res: AxiosResponse<Post_dao[]>) => {
const { data } = res;
if (data) {
dispatch(postList_action(data));
}
})
.catch((error) => {
message.error(error.message);
});
}
}
};
updateData();
}, [posts, dispatch]);
};
//数据绑定
const mapStateToProps = (state: any) => {
return { posts: state.posts };
};
export default connect(mapStateToProps)(PostListPage);
4.2 hooks 重构
使用 react-redux 提供的 hooks 函数,可以省去 connect()
,进一步优化代码
然后流程:
- 组件--> store--> dispatch(action)--> reducer处理--> store变更 --> useSelector
- 组件重新渲染
- 页面更新
const PostListPage = (props: Props) => {
const { history } = props;
const dispatch = useDispatch();
//数据绑定
const posts: Post_dao[] = useSelector((state: any) => {
return state.posts;
});
useEffect(() => {
const updateData = () => {
if (posts) {
if (posts.length <= 0) {
ajax.get("/data/postList.json")
.then((res: AxiosResponse<Post_dao[]>) => {
const { data } = res;
if (data) {
dispatch(postList_action(data));
}
})
.catch((error) => {
message.error(error.message);
});
}
}
};
updateData();
}, [posts, dispatch]);
};
export default PostListPage;
虽然已经简化了代码,但是目前还没有支持异步任务处理。关于异常任务的中间件选择 thunks-sagas-observables,官方建议先 thunk 然后再 saga。
五、redux-thunk 重构
然后流程:
- 组件--> store--> dispatch(异步action)--> 执行异步任务 --> dispatch(同步action)
- reducer处理--> store变更 --> useSelector
- 组件重新渲染
- 页面更新
<!--1.导入中间件-->
<!--milddleware.ts-->
const middlewareEnhancer = applyMiddleware(reduxThunk);
export default middlewareEnhancer;
<!--2.使用中间件-->
<!--store.ts -->
import { createStore } from "redux";
import reducer from "./reducer";
import { composeWithDevTools } from "redux-devtools-extension";
import middlewareEnhancer from "./milddleware";
export default createStore(reducer, composeWithDevTools(middlewareEnhancer));
<!--3.添加异步action-->
export const postList_action_a = () => {
return (dispatch: Dispatch, getState: any) => {
const state = getState();
if (state.posts.list && state.posts.list.length <= 0) {
dispatch(postStatus_action(PostStatus.loading, null));
return ajax
.get("/data/postList.json")
.then((res: AxiosResponse<Post_dao[]>) => {
setTimeout(() => {
dispatch(postStatus_action(PostStatus.success, null));
const { data } = res;
if (data) {
dispatch(postList_action(data));
} else {
dispatch(postList_action([]));
}
}, 2000);
})
.catch((error) => {
dispatch(
postStatus_action(PostStatus.failed, error.message)
);
});
}
};
};
export const PostListType = "postList";
export const postList_action = (data: Post_dao[]) => {
return {
type: PostListType,
data,
};
};
<!--4.重构组件-->
const PostListPage = (props: Props) => {
const { history } = props;
const dispatch = useDispatch();
const posts: Post_dao[] = useSelector((state: any) => {
return state.posts.list;
});
const status: PostStatus = useSelector((state: any) => {
return state.posts.status;
});
useEffect(() => {
const updateData = () => {
dispatch(postList_action_a());
};
updateData();
}, [posts, dispatch]);
};
export default PostListPage;
六、@reduxjs/toolkit 重构
流程 同 redux-thunk 没有变化,主要优化了redux本身使用代码。
- 简化了 store 配置
- 合并 action 和 reducer 到一个 slice 文件中
- 简化不可变数据处理
- 自动生成大部分 action
- reducer 自动匹配,避免 switch 语句
<!--reducer.ts-->
import posts from "../page/post/slice/postSlice";
import users from "../page/post/slice/userSlice";
export default combineReducers({ posts, users });
<!--简化了 store 配置-->
<!--store.ts-->
import reducer from "./reducer";
import { configureStore } from "@reduxjs/toolkit";
export default configureStore({ reducer });
<!--合并 action 和 reducer-->
<!--postSlice.ts-->
export const postSlice = createSlice({
name: "posts",
initialState,
reducers: {
postStatus: (
state: any,
action: PayloadAction<{
status: PostStatus;
message: string | null;
}>
) => {
state.status = action.payload.status;
state.message = action.payload.message;
},
postList: (state: any, action: PayloadAction<Post_dao[]>) => {
<!--简化不可变数据处理-->
state.list = action.payload;
},
},
});
export const postList_a = () => {
return (dispatch: Dispatch, getState: any) => {
const state = getState();
if (state.posts.list && state.posts.list.length <= 0) {
dispatch(postStatus({ status: PostStatus.loading, message: null }));
return ajax
.get("/data/postList.json")
.then((res: AxiosResponse<Post_dao[]>) => {
setTimeout(() => {
dispatch(
postStatus({
status: PostStatus.success,
message: null,
})
);
const { data } = res;
if (data) {
dispatch(postList(data));
} else {
dispatch(postList([]));
}
}, 2000);
})
.catch((error) => {
dispatch(
postStatus({
status: PostStatus.failed,
message: error.message,
})
);
});
}
};
};
<!--自动生成action-->
export const { postStatus, postList } = postSlice.actions;
<!--reducer 自动匹配-->
export default postSlice.reducer;
<!--PostListPage-->
const PostListPage = (props: Props) => {
const { history } = props;
const dispatch = useDispatch();
const posts: Post_dao[] = useSelector((state: any) => {
return state.posts.list;
});
const status: PostStatus = useSelector((state: any) => {
return state.posts.status;
});
useEffect(() => {
const updateData = () => {
dispatch(postList_a());
};
updateData();
}, [posts, dispatch]);
};
export default PostListPage;
七、总结
7.1 关于收获
- redux 并没有“消灭”组件的局部 state,但是从概念上剥离维护 state 逻辑
- react-redux 通过 useSelector 或者 mapStateToProps 剥离(转移/隐藏)绑定组件逻辑
- redux-thunk 以中间件的方式弥补 redux 不能异步缺陷
- 通过引入新概念(store、reducer、action、slice、thunk)和中间环节(组件绑定、中间件),把状态维护逻辑剥离出去,与组件解耦,使组件更纯粹
7.2 关于要不要用
- 官方不建议无脑使用
- 但是还是要无脑使用
7.3 关于 @reduxjs/toolkit
- 用的很方便,但是不利于学习
- 直接隐藏了不可变数据处理,与 React 关于不可变数据处理要求“相悖”
- 直接屏蔽了 action 对象、生成函数,不利于 redux 3大概念理解
参考资料
- why-use-react-redux
- react-redux-history-implementation
- 普通用法:connecting-the-components
- hooks 用法 react-redux-hooks
- 异步中间件选择:thunks-sagas-observables
- 调试工具用法 redux-devtools-extension
- redux-thunk 用法 redux-thunk
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!