以前都没用过快速构建工具来搭react环境,这次通过学习来走一遍,并且记录下知识点(纯属个人愚见)
1.构建出现public文件目录
架手架 create-react-app demo 后生成public文件-----> public文件 是放一些静态资源的,是不会参与构建的,你输入是怎样,最终打包构建还是怎样,不像src文件下的代码会参与打包构建
2.自定义数据.json 一些操作指令 比如:test文件下有api文件下的data.json
test ls---> 出现 apitest ls api/---> api文件下出现data.jsontest vi api/data.json-----> 进入到 .json 文件左下角 :q------> 就是退出该文件
3.create-react-app 已经集成了代理转发的功能,需要在package.json中配置 (要先把mock数据放到mock服务器上并配置5000端口,比如mock服务器上有很多.json文件)
"proxy":{
"/api":{ (例子)
"target": "http://localhost:5000"
}
}
4.直接把mock数据放在public文件夹下
public文件夹
mock文件夹
data.json文件
{"data":"test"}
localhost:3000/mock/data.json 这样也能拿到mock的模拟数据
public文件下的内容会原封不动的打包到整个项目当中,是一些静态资源
5.父级传参到子级,可以通过...扩展运算符来传递
todos是父级传给子级的props todos = [{id:1,text:"1111"},{id:2,text:"2222"}]
todos.map(todo=>{
return <Todo key={todo.id} {...todo}/>
})
在子组件下,用 this.props 方法可查看父级传给子级所有属性
6.通过...扩展运算符往旧数组里加数据
const todo = {
id: this.indexId++,
text: text,
completed: true
}
const newTodoArr = [...this.state.todoArr,todo]
7.通过...扩展运算符往旧对象里里更改某个数据
如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉,这用来修改现有对象部分的部分属性就很方便了。
const newTodo = this.state.todos.map(item=>{
return item.id === 1? {...item, completed: !item.completed} :item
})
8.用redux时,actions文件夹下一般有2个文件
index.js文件下:
import {ADD_TODO, ………等} from "./actionTypes";
export addTodo = (text)=>({
type:ADD_TODO,
text
})
……………等……………
一般情况下 type 同方法名称相同并且改成大写即可
actionTypes.js文件下:
export const ADD_TODO = "ADD_TODO";
…… …… ……
9.用reducer时,reducer文件夹下的index.js文件
const initState = {
filter: "",
todos: [],
text: ""
}
const todoApp = (state = initState, action)=>{
switch(action.type){
case ADD_TODO:
return {
...state,
todos:[
...state.todos,
{
id: action.id,
text:action.text,
completed: false
}
]
}
case SET_FILTER:
…… ……
case SET_TODO_TEXT:
…… ……
default :
return state
}
}
export default todoApp;
reducer里的state是不能直接操作的,必须返回一个新的state,返回新的state对象的方法有很多,比如 Object.assign({},……)也是其中一种方法
10. index.js文件下的 reducer拆分
其中一个例子,分解reducer中的 SET_TODO_TEXT
新建一个text.js 用来拆分 SET_TODO_TEXT
import {…… ……} from ""../actions/actionTypes;
const text = (state = "", action)=>{
switch (action.type){
case SET_TODO_TEXT:
return action.text
default:
return state
}
}
export default text
分解完上面那个大reducer之后,把reducer文件夹中的index里面的大reducer删掉,逐步引入拆分的各个小的reducer
index.js:
import { combineReducers } from "redux";
import text from "./text";
import …… ……
export default combineReducers({
text,
…… ……
});
必须安装redux这个依赖 npm install redux 因为create-react-app 并没有安装redux
11. 在src文件夹下创建 store.js
import { createStore } from "redux";
import rootReducer from "./reducers";
import { addTodo ,…… …… } from "./actions";
export const store = createStore(rootReducer);
// 得到初始的state
store.getState()
// 订阅state的变化
store.subscribe(()=>{
console.log(store.getState)
})
// 发生actions
store.dispatch(addTodo("11111"));
12. 集成react-redux(容器型组件拆分,编写 )
向根组件注入 Store -> Provider 组件连接React组件和redux状态层 -> connect获取React组件所需的State和Actions -> map api先安装react-redux这个依赖 npm install react-redux 因为create-react-app 并没有安装react-redux
例子:在src下创建containers文件夹,创建TodoListContainer.js文件 容器组件
import { connect } from "react -redux";
import { toggleTodo } from "../actions";
import TodoList from "../components/TodoList";
const mapStateToProps = (state)=>({
todos: state.todos
})
const mapDispatchToProps = (dispatch)=>({
toggleTodo: id => dispatch(toggleTodo(id))
})
export default connect(
mapStateToProps, //redux中的state映射到react组件当中的props上
mapDispatchToProps //react组件需要使用的action方法,映射到组件的props当中
)(TodoList);
connect()属于高阶组件,将完成react层和redux层的逻辑连接
例子:在src下的components文件夹的App.js文件下 引入containers里的容器组件
import { TodoListContainer } from "../containers/TodoListContainer";
import …… ……
return (
<div>
<TodoListContainer/>
…… ……
</div>
)
例子:在src下的index.js 入口文件
import { createStore } from "redux";
import { Provider } from "react-redux";
import rootReducer from "./reducers";
const store = createStore(rootReducer);
<Provider store={store}>
<App/>
</Provider>
13. 异步Action 有时action里会通过调用api拿到一些数据,然后再dispatch(),这样就存在异步,数据会有延迟
例子:
const getTodosSuccess = (data)=>({
type: "TODOS_SUCCESS",
data
})
const getTodosFailure = (error)=>({
type: "TODOS_FAILURE",
error
})
export const getTodos = ()=> dispatch =>{
return new Promise((resolve,reject) => {
axios.get({ url: 'xxxxxxxxx'}).then(res=>{
dispatch(getTodosSuccess(res))
resolve(res)
}).catch(err=>{
dispatch(getTodosFailure(err))
reject(err)
})
})
}
getTodos()返回值是一个函数,这个函数我们是无法处理的,所以要借助redux的中间件来完成异步action这个操作
最常用的是 npm install redux-thunk
14. 异步Action 要用中间件解决 redux-thunk
例子: 在src目录的 index.js下
import thunkMiddleware from "redux-thunk";
import { createStore, applyMiddleware } from "redux";
const store = createStore(rootReducer, applyMiddleware(thunkMiddleware))
这样创建出来的store 就能处理异步action了
15. 谷歌浏览器安装redux调试工具
在github上寻找 redux-devtools-extension
找到 Advanced store setup
if you setup your store with middleware and enhancers,change:
xxxxxxxxxxxxxxxxxxxxxxxxxxx 这里的东西可以用,复制下来
在src目录的 index.js下
import { compose } from "redux"; //compose这个函数可以帮助我们将多个enhancers组合到一起,compose是个增强器可以增加store的功能
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION__COMPOSE__ || compose;
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(thunkMiddleware))
);
16. 项目结构组件方式
按照类型:
action/
-- action1.js action2.js
components/
-- component1.js component2.js
reducers/
-- reducer1.js reducer2.js index.js(所有reducer组合成一个)
containers/ (容器型组件)
-- containser1.js containser2.js
按照功能模块:(存在不同模块之间状态互相耦合的场景)
feature1/
-- components/
-- Container.js
-- actions.js
-- reducer.js
feature2/
-- components/
-- Container.js
-- actions.js
-- reducer.js
Ducks: …………
17. Middleware(中间件) 增强store dispatch的能力
例子:redux-thunk 可以处理函数类型的action了
View -> ( mid1 -> mid2 -> …… …… ) -> reducer //页面触发action 会逐步经过这些中间件,增强dispatch,dispatch所做的事就是派发action
Function:({getState, dispatch}) => next => action
在src文件夹下 新建 Middleware文件夹->logger.js
// 打印action, state 的logger中间件
const logger = ({getState, dispatch}) => next => action => {
console.group(action.type);
console.log("dispatching:", action);
const result = next(action);
console.log("next state:", getState());
consoel.groupEnd();
return result;
}
export default logger;
在index.js文件下 ,把自己写的中间件加进去
import loggerMiddleware from "./middleware/logger";
const store = createStore(
…… ……
applyMiddleware(…… ……,loggerMiddleware)
)
18. store enhancer 增强redux store的功能
store enhancer 一般结构:
function enhancerCreator(){
return createStore => (...arg) => {
// do something based old store
// return a new enhanced store
}
}
在src文件夹下 新建enhancer文件夹 -> logger.js
// 打印action, state 的增强器
const logger = createStore => (...arg) => {
const store = createStore(...arg);
const dispatch = (action) => {
console.group(action.type);
console.log("dispatching:", action);
const result = store.dispatch(action);
console.log("next state:", store.getState());
consoel.groupEnd();
return result;
}
return {...store, dispatch} // 把新的dispatch方法覆盖到原来对象的dispatch
}
export default logger;
在index.js文件下 ,把自己写的enhancer加进去
import loggerEnhancer from "./enhancer/logger";
const store = createStore(
…… ……
compose(applyMiddleware(thunkMiddleware), loggerEnhancer)
)
compose是可以把多个 store enhancer 进行组合的
在平时中应该尽量多使用 Middleware 来增强dispatch的能力, 慎用 store enhancer 记住!
18. Immutable.js 可以用来创建不可变的对象state 每次都可以返回个新的
npm install immutable
npm install redux-immutable
import Immutable from "immutable";
import { combineReducers } from "redux-immutable";
const initState = {
…… ……,
isFetching: false,
todos: {
……,
data: []
}
}
//这样就创建了一个不可变的 immutable 对象
const state = Immutable.fromJS(initState)
//设置属性,一次只能设置一个key
state.set("isFetching", true);
//merge可以一下设置多个
state.merge({
isFetching: false,
todos: Immutable.fromJS(action.data) // 数据类型一定要统一用immutable的类型
});
//get得到属性的值
state.get("isFetching");
//getIn 从外层到内层逐层获取
state.getIn(["todos","data"]);
//把immutable对象转化成普通JS对象
state.toJS();
因为在 mapStateToProps,…… 的对象要普通JS对象才能使用
在reducers文件下的 index.js中
import { combineReducers } from "redux-immutable"; // redux中的combineReducers只能识别普通JS对象
19. Reselect库经常和redux一起使用, Reselect减少state的重复计算
npm install reselect
import { createSelector } from "reselect"
const getFilter = (state) => state.filter;
const getTodos = (state) => state.todos.data;
export const getVisibleTodos = createSelector(
[getTodos,getFilter], //数组里面是其他所依赖的select函数
(todos, filter) => { //前2个函数返回的数据结果,可用来计算
console.log("-----"); //用来测试看有没多余的重复执行
switch(filter){
case: "xxx":
return
case ……:
……
default:
……
}
}
);
只有当select函数确实非常复杂并且整个redux性能有很大的问题才去考虑用reselect,并不是必须的
20. Router相关库
react-routerreact-router-domreact-router-native建议在项目之中使用<BrowserRouter>组件
react-router这个库实现了路由的核心功能,react-router-dom是在react-router之上的一层封装,将react-router的路由功能和web的API进行了绑定
<Router>常用组件: <BrowserRouter>
1 . Html5 history API (pushState, repalceState等)
2. 需要Web服务器额外配置
localhost:3000/home localhost:3000/about
<Router>常用组件: <HashRouter>
1. 使用url的hash部分作为路由的信息
2. 主要为了兼容老版本浏览器
localhost:3000/#/home localhost:3000/#/about
21. 路由配置:Route
<BrowserRouter>
<Route path="/" exact component={Home}/>
<Route path="/about" component={About}/>
<Route path="/user/:aaa" component={User}/>
…… ……
<BrowserRouter/>
组件中得到传过来的参数: this.props.match.params.aaa
22. 路由匹配
<Route exact /> exact : url和path完全相同的情况下才会匹配成功
import { Switch } from "react-router-dom";
<Switch>
<Route path="/about" component={About}/>
<Route path="/user/:aaa" component={User}/>
<Route path="/" component={Home}/> // 当前面都匹配不成功时,根路径才会被匹配
<Switch/>
Swicth 的功能是会去匹配第一个,被匹配到的<Route />,当第一个<Route/>匹配到的时候,其他的<Rpute>都不会被匹配
23. 路由渲染组件的方式
<Route component /><Rute render /><Route children />
<Route path="/about" render={
(props) => <About {...props}/> //props是一个对象,包含了match,location,history
} />
在App.js:
<Router>
<Route path="/user" component={User}>
</Router>
在User.js:
<div>
<Route path={`${this.props.match.path}/:user`} component={UserDetail}>
</div>
在UserDetail.js:
<div>
this.props.match.params.user
</div>
24. create-rwact-app 创建出来的默认的入口文件
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
serviceWorker 离线存储,加载优化的事情, 做项目时没使用到的话要把这句话删掉
25. store.js 文件中的 process.env.NODE_ENV 环境变量 && 判断浏览器有没装redux调试工具
判断当年程序的执行是开发环境还是生产环境 && 如果浏览器安装redux的调试工具 window对象下会有redux的调试方法添加进去
if (
process.env.NODE_ENV !== "production" &&
window.__REDUX_DEVTOOLS_EXTENSION__
){
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; // 作为store的增强器
store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk))); // 将REDUX_DEVTOOLS的功能增强到store当中
} else {
store = createStore(rootReducer, applyMiddleware(thunk));
}
26. fetch()或者 axios 请求url
axios 优缺点:
- 从 node.js 创建 http 请求。
- 支持 Promise API。
- 提供了一些并发请求的接口(重要,方便了很多的操作)。
- 在浏览器中创建 XMLHttpRequests。
- 在 node.js 则创建 http 请求。(自动性强)
- 支持 Promise API。
- 支持拦截请求和响应。
- 转换请求和响应数据。
- 取消请求。
- 自动转换 JSON 数据。
- 客户端支持防止CSRF。
- 客户端支持防御 XSRF。
Fetch 链接 https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch (MDN)
axios既提供了并发的封装,也没有fetch的各种问题,而且体积也较小,当之无愧现在最应该选用的请求的方式。
27. 中间件的业务流程
文件 ./entities/products.js
export const schema = {
name: "products",
id: "id"
}
…… …… ……
*****************************************************
文件 ./redux/home.js
import FETCH_DATA from "../middleware/api"
import schema from "./entities/products"
export const types = {
FETCH_LIKES_REQUEST:"FETCH_LIKES_REQUEST",
FETCH_LIKES_SUCCESS:"FETCH_LIKES_SUCCESS",
FETCH_LIKES_FAILURE:"FETCH_LIKES_FAILURE"
}
export const actions = {
loadLikes: ()=>{
return (dispatch, getState) => {
const endpoint = url.getProductList(0,10); // utils中的方法返回一个url路径
return dispatch(fetchLikes(endpoint));
}
}
}
const fetchLikes = (endpoint)=>({
[FETCH_DATA]: {
types:[
types.FETCH_LIKES_REQUEST,
types.FETCH_LIKES_SUCCESS,
types.FETCH_LIKES_FAILURE
],
endpoint,
schema
},
});
…… …… ……
*****************************************************
文件 middleware/api.js
export const FETCH_DATA = "FETCH_DATA";
export default store => next => action => {
const callAPI = action[FETCH_DATA];
if(typeof callAPI === "undefined"){
return next(action);
}
const { endpoint, schema, types } = callAPI
if(typeof endpoint !== "string"){
throw new Error("endpoint要为字符串类型的url");
}
if(!schema){
throw new Error("必须指定领域实体的schema");
}
if(!Array.isArray(types) && types.length !== 3){
throw new Error("需要指定一个包含了3个action type的数组");
}
if(!types.every(type => typeof type === "string")){
throw new Error("action types 必须为字符串类型");
}
// 里面有新东西,增强dispatch()
const actionWith = (data) =>{
const finalAction = {...action, ...data};
delete finalAction[FETCH_DATA];
return finalAction
}
const [requestType, successType, failureType] = types;
next(actionWith({type: requestType}));
return fetchData(endpoint, schema).then(
response => next(actionWith({
type: successType,
response
})),
error => next(actionWith({
type: failureType,
error: error.message || "获取数据失败"
}))
)
};
// 执行网络请求
const fetchData = (endpoint, schema) => {
return get(endpoint).then(data => {
return normalizeDate(data,schema)
})
}
// 根据schema,将获取的数据扁平化处理
const normalizeDate = (data,schema) => {
const { id,name } = schema;
let kvObj = {};
let ids = [];
if(Array.isArray(data)){
data.forEach(item => {
kvObj[item[id]] = item;
ids.push(item[id]);
})
}else{
kvObj[data[id]] = data;
ids.push(data[id]);
}
return {
[name]: kvObj,
ids
}
}
28. 对于一些从redux取得的数据加载,判断下是否可以避免重复请求
// 对于一些变化频率不高的数据不需要重复请求,比如首页进详情页,详情页又回到首页的某些数据,可以直接拿redux里的缓存
this.removeListener = false;
componentDidMount(){
// 凡是要加载数据都要多想想什么情况要加载,什么时候可以避免运算一些逻辑
if(this.props.pageCount < 3){
document.addEventListener("scroll", this.handleScroll);
}else{
this.removeListener = true;
}
// 不是每次都要加载数据,通过一些判断
if(this.props.pageCount == 0){
this.props.fetchData();
}
}
componentDidUpdate(){
if(this.props.pageCount >= 3 && !this.removeListener){
document.removeEventListener("scroll", this.handleScroll);
this.removeListener = true;
}
}
componentWillUnmount(){
if(!this.removeListener){
document.removeEventListener("scroll", this.handleScroll);
}
}
29. 删除对象里的某个键值对 ,key:value 解构出来的对象,需要删掉某个key的键值对
const finalAction = {...action, ...data}
delete finalAction[FETCH_DATA]
return finalAction
var a = {"a":"a","b":"b","c":"c"}
delete a.a
a = {b: "b", c: "c"};
30. React.createRef()
Refs 提供了一个获得DOM节点或者创建在render方法中的React元素的方法
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
访问Refs:
当一个ref通过render放入一个元素中,一个对节点的引用可以通过ref的current属性得到;
const node = this.myRef.current;
比如:
this.myRef.current.offsetHeight --- 就能得到DOM节点的高度
31. 解构出来的对象,需要删掉某个key的键值对
const actionWith = data => {
const finalAction = {...action, ...data}
delete finalAction[FETCH_DATA]
return finalAction
}
var a = {"sdf":111,"d":"sdfsdfsdf","23":11111}
delete a["d"]
a = {23: 11111, sdf: 111}
32.this.props.history 返回上一页
<Router>
<Switch>
<Route />
………… ……
</Switch>
</Router>
handleBack = () => {
this.props.history.goBack();
}
需要在路由包裹里面才能生效
32.push | Link 跳到某个页面
this.props.history.push("/");
this.props.history.push("/search");
<Link to="/search" className="">xxxxx</Link>
<Route path="/search" component={Search}/>
33.
标签要加alt属性,不然会报黄色警告
<img alt="" />
34. 异步actions 中的方法
const rootReducer = combineReducers({
entities,
login,
…… …… ……… ……
}
const initialState = {
username: localStorage.getItem("username") || "",
password: ''
}
const login = (state=initialState, action) => {
switch(){
case:
…… …… ……
}
}
export default login;
login: () => {
return (dispatch, getState) => {
const {username, password} = getState().login;
dispatch({type:…… ……});
}
}
getState() 得到reducer中的所有数据,login是combineReducers其中一个,这又和上层Login组件有着联系
connect(
mapStateToProps,
mapDispatchToProps
)(Login);
35. 和上面连起来一起使用
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { actions as loginActions, getUsername, …… } from ……
// selectors
export const getUsername = (state) => state.login.username;
// action creators
export const actions = {
login:() => …… ……
}
const mapStateToProps = (state, props) => {
return {
username: getUsername(state), // 这里的state是数据,所以并不用state()得到,直接传递即可
…… …… ……
}
}
const mapDispatchToProps = (dispatch, props) => {
return {
loginActions: bindActionCreators(loginActions, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Login);
36. bind(this, 参数)
handleClick = (item) => {
this.props.onClickItem(item);
}
<span key={item.id}
className="popularSearch__item"
onClick={this.handleClick.bind(this, item)}
>{item.keyword}</span>
onClick事件要往其父级传参
37. location这个属性是react-router注入的
const { location: {state} } = this.props;
if(state && state.from){
return <Redirect to={state.from}/>
}
38. 有时候route需要一些判断
<Router>
<Switch>
<Route exact path="/" component={Home}/>
…… …… ……
<PrivateRoute path="/user" component={User}/>
<PrivateRoute path="/purchase/:id" component={Purchase}/>
</Switch>
</Router>
class PrivateRoute extends React.Component{
render(){
const { component: Component, login, ...rest } = this.props;
return (
<Route
{...rest}
render={(props) => {
return login ? (<Component {...props}/>) : (<Redirect to={{
pathname: "/login",
state: {from: props.location}
}}/>)
}}
/>
)
}
}
const mapStateToProps = (state,props) => {
return {
login: isLogin(state)
}
}
export default connect(
mapStateToProps,
null
)(PrivateRoute) ;
…… …… ……
…… …… ……
39. this.props 取值
const {
data: { title, statusText, orderPicUrl, channel, text, type, commentId }, isCommenting
} = this.props;
40. +new Date() 与 new Date()的区别
typeof new Date() "object"
typeof +new Date() "number"
new Date()
Mon Oct 14 2019 09:32:06 GMT+0800 (中国标准时间)
+new Date()
1571016720402
41. mapDispatchToProps也可以从别处引来action, mapStateToProps中的state,props用法
const mapStateToProps = (state, props) => {
const productId = props.match.params.id;
return {
product: getProduct(state, productId),
…… ……
}
};
const mapDispatchToProps = (dispatch) => {
return {
purchaseActions: bindActionCreators(purchaseActions, dispatch),
detailActions: bindActionCreators(detailActions, dispatch)
}
}
42. asyncComponent 按需加载 dynamic import()实现动态加载模块功能的
项目跑起来后,network里会加载个 bundle.js 这个js文件涵盖了项目中所有js资源和所有js代码,因此这个bundle.js会比较大在项目里不点点击切换页面路由,但是network中并没有加载新的js代码,依然只有bundle.js,显然对性能是有影响的, 我们要做的就是,访问不同页面加载不同页面的JS代码
import("../Login") 传入组件的路径,这里的import并不是一个函数而是一个运算符,返回结果是一个promise对象,
当组件加载完成之后,promise状态会变成完成,那么我们就可以正常使用
我们需要定义一个高阶组件 function(){ import("../Login") }, 调用的时候会执行里面的代码
const User = AsyncComponent(() => import("../User"));
const Purchase = AsyncComponent(() => import("../Purchase"));
<Router>
<Switch>
<PrivateRoute path="/user" component={User} />
<PrivateRoute path="/purchase/:id" component={Purchase}/>
…… …… ……
</Switch>
</Router>
export default function asyncComponent(importComponent){
class AsyncComponent extends React.Component{
constructor(props){
super(props);
this.state = {
component: null
}
}
componentDidMount(){
importComponent().then((mod) => {
this.setState({
component: mod.default
})
})
}
render(){
const C = this.state.component;
return C ? <C {...this.props}/> : null
}
}
return AsyncComponent;
}
改成这样之后,在network里面可以看到 bundle.js 这是项目的骨架代码,1.chunk.js这是首页的js代码,
等下每点去另一个页面都会加载一个不同页面的 chunk.js
npm run build 之后
也可以在控制台看到代码被编译之后是分片的…… 0,1,2,3……chunk.js,骨架代码是 main.js
43. "reselect": "^4.0.0" 的用法
- 在 reducer 中,都是返回一个新对象,否则组件是不会更新 ui 的。而返回新对象,可以:
- Object.assign:比较 hack,每次新生成一个对象
- ...state
- immutable.js
- 主要用来缓存衍生数据的:就是从 store 中的 A、B 要衍生出 C,就可以使用 reselect 来优化性能,避免重复计算。
<div>
<Count /> // 比如设置 setInterval定时器1000ms 执行一次Count
<Posts />
</div>
这样本没有什么问题,但是,一旦 Posts 的 mapStateToProps 是需要产生衍生数据的,即使 Posts 的 reducer 没有变化,
也还是要随着 Count 每 1000ms 计算,浪费性能
Count 每 1000ms 执行一次自加,不光 Count 的 reducer 会过一遍 mapStateToProps,
Posts 的 reducer 也会过一遍。这是必然的,因为 connect 就是这样设计的。
const mapStateToProps = state => {
const postsById = state.postsById
const usersById = state.usersById
const postListing = state.postListing
return {
posts: postListing.map(id => {
const post = postsById[id];
return {...post, user: usersById[post.author]}
})
}
}
使用 reselect 优化:
这样子,就可以做到:只有 postsById、usersById、postListing 其中之一变化时才会重新计算 posts
const getListing = createSelector(
state => state.postsById,
state => state.usersById,
state => state.postListing,
(posts, users, listing) => listing.map(id => {
const post = posts[id];
return {...post, user: users[post.author]}
})
);
const mapStateToProps = (state) => {
return {posts: getListing(state)};
};
结语
前端react QQ群:
788023830----React/Redux - 地下老英雄前端交流 QQ群:
249620372----FRONT-END-JS前端(我们的宗旨是,为了加班,为了秃顶……,仰望大佬),希望小伙伴们加群一起学习