react进阶
1. 虚拟dom
来看下对比:
image image
react native可以开发原生应用的原因是因为虚拟dom,在浏览器中虚拟dom可以生成网页,在原生应用中可以生成其他组件
虚拟dom比对,diff算法,同级比对
当最上层的节点就不一致时,react就不会往下比对了,直接删掉该节点下的所有dom,直接重新生成
image给每个节点加key值比较就简单多了,不用像左图一样再去循环比对
2. 测试
通用测试框架 jest:https://jestjs.io/docs/zh-Hans/getting-started
react官方推荐:https://airbnb.io/enzyme/
首先安装安装
npm install enzyme-adapter-react-16 enzyme --save-dev
在package.json中加上test测试命令:
"test": "react-scripts test --env=jsdom"
具体用法可参考文档
3. PropTypes类型检查
proptypes类型检查工具:https://react.docschina.org/docs/typechecking-with-proptypes.html
简单的demo
Home.propTypes = {
data: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
history: PropTypes.object,
}
4. 引入antd组件并实现按需加载
安装ui组件: antd,安装babel-plugin-import可以实现antd按需加载
配置babal-plugin-import: https://blog.csdn.net/u010565037/article/details/86154544
5. react router
目前使用的是react-router-dom
参考文档:http://react-china.org/t/react-router4/15843
详细使用方法:https://www.jianshu.com/p/97e4af32811a
image image image imageimage image image image
React-异步组件withRouter用法:
https://www.cnblogs.com/superlizhao/p/9497021.html
withRouter作用:
https://www.cnblogs.com/luowenshuai/p/9526341.html
6. createContext
Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。
参考文档:https://react.docschina.org/docs/context.html
我们首先建一个AppContext.js文件
import React from 'react'
export const AppContext = React.createContext()
然后我们建一个createContext的高阶组件WithContext.js
import React from 'react'
import { AppContext } from './AppContext'
const withContext = (Component) => {
return (props) => (
<AppContext.Consumer>
{({ state, actions }) => {
return <Component {...props} data={state} actions={actions} />
}}
</AppContext.Consumer>
)
}
export default withContext
在app.js中注册
<AppContext.Provider value={{
state: this.state,
actions: this.actions,
}}>
<Router>
<div className="App">
<div className="container pb-5">
<Route path="/" exact component={Home} />
<Route path="/create" component={Create} />
<Route path="/edit/:id" component={Create} />
</div>
</div>
</Router>
</AppContext.Provider>
7. redux
参考文档:https://www.redux.org.cn/
安装react-redux(作用是将顶层组件包裹在Provider组件之中,这样的话,所有组件就都可以在react-redux的控制之下了,但是store必须作为参数放到Provider组件中去)
详情参考:http://cn.redux.js.org/docs/react-redux/
react与react-redux: https://www.cnblogs.com/bax-life/p/8440326.html
来看一个简单的图例:
image
首先我们需要安装redux与react-redux
yarn add redux react-redux --save
redux的集成:
创建action模块
创建reducer模块
创建store模块
通过connect方法将react组件和redux连接起来
添加provider作为项目根组件,用于数据存储
redux调试工具安装
- 在chrome中安装Redux Devtools扩展
- 在项目中安装redux-devtools-extension
来看一个简单的redux例子:
新建store文件:创建index.js与reducer.js
reducer.js
const defaultState = {
inputValue: '',
list: []
}
export default (state = defaultState, action) => {
if(action.type === "change_input_value") {
const newState = JSON.parse(JSON.stringify(state));
newState.inputValue = action.value;
return newState;
}
if(action.type === "add_item") {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue = '';
return newState;
}
return state;
}
index.js
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
app.js
import { Provider } from 'react-redux';
import store from './store';
const App = (
<Provider store={store}>
<TodoList />
</Provider>
)
组件todolist.js
import React from 'react';
import { connect } from 'react-redux';
const TodoList = (props) => {
const { inputValue, changeInputValue, handleClick, list } = props;
return (
<div>
</div>
)
}
const mapStateToProps = (state) => {
return {
inputValue: state.inputValue,
list: state.list
}
}
// store.dispatch props
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e) {
const action = {
type: "change_input_value",
value: e.target.value
}
dispatch(action);
},
handleClick() {
const action = {
type: 'add_item'
}
dispatch(action);
}
}
}
// connect执行返回结果是一个容器组件
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
redux = reducer + flux
来看下图:
image
修改store组件->action->reducer->store
redux中间件:
image
注意事项:
- store是唯一的
- 只有store能够改变自己的内容
- reducer必须是纯函数
- reducer并没有改变store的内容,他生成了深拷贝,然后将值又返回给了store
开发中写法:
新建store文件夹,新建index.js与reducer.js文件
reducer.js
// 可以在每个组件中设置reducer,然后在总的reducer中引入它们
import { combineReducers } from 'redux-immutable'; // 创建combineReducers与Immutable.js状态一起使用的Redux的等效函数
import { reducer as headerReducer } from '../common/header/store';
import { reducer as homeReducer } from '../pages/home/store/';
import { reducer as detailReducer } from '../pages/detail/store/';
import { reducer as loginReducer } from '../pages/login/store/';
const redcuer = combineReducers({
header: headerReducer,
home: homeReducer,
detail: detailReducer,
login: loginReducer
});
export default redcuer;
index.js
// redux-devtools-extension 可视化工具
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'; // 实现ajax异步请求,使得支持action中为一个异步请求函数
import reducer from './reducer';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(
applyMiddleware(thunk)
));
export default store;
这里以home中的为例,来看一个完整的过程
home文件夹store中的index.js
import reducer from './reducer';
import * as actionCreators from './actionCreators';
import * as actionTypes from './actionTypes';
export { reducer, actionCreators, actionTypes };
reducer.js
import { fromJS } from 'immutable';
import * as actionTypes from './actionTypes';
// immutable为了防止不小心改变state,所以用immutable(不可改变的数据)
const defaultState = fromJS({
topicList: [],
articleList: [],
recommendList: [],
articlePage: 1,
showScroll: false
});
const changeHomeData = (state, action) => {
return state.merge({
topicList: action.topicList,
articleList: action.articleList,
recommendList: action.recommendList
});
}
const addHomeList = (state, action) => {
return state.merge({
'articlePage': action.nextPage,
'articleList': state.get('articleList').concat(action.list)
});
}
export default (state = defaultState, action) => {
switch(action.type) {
case actionTypes.CHANGE_HOME_DATA:
return changeHomeData(state, action);
case actionTypes.ADD_HOME_LIST:
return addHomeList(state, action);
case actionTypes.TOGGLE_TOP_SHOW:
return state.set('showScroll', action.show)
default:
return state;
}
}
actionCreators.js
import * as actionTypes from './actionTypes';
import { fromJS } from 'immutable';
import axios from 'axios';
const addHomeList = (list, nextPage) => ({
type: actionTypes.ADD_HOME_LIST,
list: fromJS(list), // List方法,将普通数组转换为immutable数组
nextPage
})
export const getMoreList = (page) => {
return (dispatch) => {
axios.get('/api/homeList.json?page='+page)
.then((res) => {
const result = res.data.data;
dispatch(addHomeList(result, page + 1));
})
}
}
export const toggleTopShow = (show) => ({
type: actionTypes.TOGGLE_TOP_SHOW,
show
})
actionTypes.js
export const CHANGE_HOME_DATA = 'header/CHANGE_HOME_DATA';
export const ADD_HOME_LIST = 'header/ADD_HOME_LIST';
export const TOGGLE_TOP_SHOW = 'header/actionTypes.TOGGLE_TOP_SHOW';
组件index.js
class Home extends PureComponent { // PureComponent底层实现了shouldComponentUpdate,以防止state某些数据更新时所有页面都要更新, PureComponent最好用immutable,否则就不要用,用shouldComponentUpdate即可
}
const mapState = (state) => ({
showScroll: state.getIn(['home', 'showScroll'])
})
const mapDispatch = (dispatch) => ({
changeHomeData() {
dispatch(actionCreators.getHomeInfo());
},
changeScrollTopShow() {
if (document.documentElement.scrollTop > 100) {
dispatch(actionCreators.toggleTopShow(true))
} else {
dispatch(actionCreators.toggleTopShow(false))
}
}
})
export default connect(mapState, mapDispatch)(Home);