本篇是《从0到1搭建react项目》的第二篇,主要介绍react路由配置、数据层和容器层实现。
相关文章:React-从0到1搭建一个React项目(一)
react-router基本已成为react路由的标准解决方案。我使用的是react-router-dom。
react-router包含了所有react-router-dom和react-router-native的公共组件,实现了路由的核心功能。通常情况下,如果是开发web应用,使用react-router-dom就能满足需求。如果是用react-native开发,就用react-router-native。
具体的一些介绍,可以参考:
路由的使用可以参考官方文档。也可以按下面的步骤来:
routes.js
文件,用来管理所有的路由。这里import了App组件,如果后续还有别的组件,import进来,增加一条Route
即可import React from 'react'
import { BrowserRouter as Router, Route } from "react-router-dom";
import App from './App'
const AppRouter = () => (
);
export default AppRouter;
index.js
文件中引入AppRouterimport React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import AppRouter from './routes'
ReactDOM.render( , document.getElementById('root'));
// 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();
到这里,实现了一个简单的路由功能。接下来实现数据层。
React Redux可以让react组件从redux store中读取数据,并且通过dispatch action更新store中的数据。详细使用可查看官方文档。
我们的数据层就是使用react-redux
来管理整个应用的状态。
React Redux提供了
组件,可以让web应用的所有组件都能操作redux store。
组件接收一个store作为参数,而创建store前,需要先定义好action和reduce。下面先来看下action的定义。
/src/actions/
目录下新建actions.js
文件,如果项目的action比较多,可以拆分成多个action.js文件。export const SAVE_USER_INFO = "SAVE_USER_INFO"
export const saveUserInfo = (userInfo) => ({
type: SAVE_USER_INFO,
userInfo
})
这里定义了一个type常量SAVE_USER_INFO,用来标识action的类型;定义了一个方法saveUserInfo,接收userInfo参数,用来更新store中的用户信息。
定义好action之后,就可以实现reduce了。
/src/reduce
目录下新建两个文件index.js
和home-reduce.js
,其中index.js
里通过redux的combineReducers
把各个业务模块的reduce组合成一个reduce,home-reduce.js
里面是业务模块的reduce,这里就是实现一个保存用户信息的操作。代码如下://index.js
import { combineReducers } from 'redux'
import home from './home-reduce'
export default combineReducers({
home
})
//home-reduce.js
import { SAVE_USER_INFO } from '../actions/actions'
const initialState = {
userInfo: null
}
const home = (state = initialState, action) => {
switch (action.type) {
case SAVE_USER_INFO:
return Object.assign({}, state, {
userInfo: action.userInfo
})
default:
return state
}
}
export default home
查看官方步骤
configureStore.js
文件,内容:import { applyMiddleware, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from '../reducers'
export default function configureStore(preloadedState) {
const middlewares = [thunkMiddleware]
if (process.env.NODE_ENV !== 'production') {
middlewares.push(createLogger())
}
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer]
const composedEnhancers = composeWithDevTools(...enhancers)
const store = createStore(rootReducer, preloadedState, composedEnhancers)
//reducer hot loading
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('../reducers', () => store.replaceReducer(rootReducer))
}
return store
}
这里面核心的代码就是const store = createStore(rootReducer, preloadedState, composedEnhancers)。
createStore传递了三个参数,第一个参数是reduce,提供了更新state的实现。preloadedState是预加载的state,服务端渲染时会用到。composedEnhancers中集成了多个Middleware,你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
在根组件上使用
,修改src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import AppRouter from './routes';
import configureStore from '../../configureStore/configureStore';
import './index.css';
const store = configureStore()
ReactDOM.render(
,
document.getElementById('root'))
到这里数据层就算完成了,接下来看下怎么在容器层调用保存用户信息的方法以及如何使用store里面的数据。
在/src/container
目录下新建一个容器组件home.js
。代码为:
import React from 'react'
import styled from 'styled-components'
import PropTypes from 'prop-types'
import { saveUserInfo } from '../actions/actions'
import { connect } from 'react-redux'
import Input from '@material-ui/core/Input';
const Container = styled.div`
display: flex;
height:100%;
width:100%;
flex-direction:column;
align-items:center;
background-color:#F0F0F0;
`
const NameFromStore = styled.div`
font-size:large;
`
class Home extends React.Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
var userInfo = {
name: event.target.value
}
this.props.saveUserInfo(userInfo)
}
render() {
return (
{this.props.userInfo && this.props.userInfo.name}
);
}
}
const mapStateToProps = state => ({
userInfo: state.home.userInfo
})
const mapDispatchToProps = dispatch => {
return {
saveUserInfo: (userInfo) => (
dispatch(saveUserInfo(userInfo))
)
}
}
Home.propTypes = {
saveUserInfo: PropTypes.func,
}
export default connect(mapStateToProps, mapDispatchToProps)(Home)
home.js
组件实现的功能:定义了一个input输入框,将输入的信息保存到store中,再从store中取出保存的信息展示出来。
home.js
中使用了styled-components
,它可以让我们在组件的js文件中直接编写css代码。传统的在css文件中编写样式的方法存在诸多弊病,比如定义的class-name是全局的,很容易导致样式覆盖。styled-components
是一个现代的解决方案,同时还提供了主题设置,方便应用样式的统一。感兴趣的可以查看官网。
正如第一篇文章的架构图中画的一样,容器层的组件和数据层reduce的交互是通过定义的action作为桥梁的。在容器层的home.js
中,把actions.js
中定义的更新store的方法import
进来,然后定义了一个mapStateToProps
方法和mapDispatchToProps
,分别定义了store中的state到组件的props映射和dispatch方法到组件props方法的映射,再通过connect
将两种映射和组件关联起来。
import { saveUserInfo } from '../actions/actions'
...
//将store中的state数据映射到组件的props属性
const mapStateToProps = state => ({
userInfo: state.home.userInfo
})
//将组件的saveUserInfo属性映射到dispatch()方法,从而通过action调用到reduce中定义的方法更新store
const mapDispatchToProps = dispatch => {
return {
saveUserInfo: (userInfo) => (
dispatch(saveUserInfo(userInfo))
)
}
}
Home.propTypes = {
saveUserInfo: PropTypes.func//定义Home的props属性
}
export default connect(mapStateToProps, mapDispatchToProps)(Home)
我们修改routes.js
的代码,将Home组件作为第一个渲染的组件,看下效果
import React from 'react'
import { BrowserRouter as Router, Route } from "react-router-dom";
import Home from './container/home'
const AppRouter = () => (
);
export default AppRouter;
效果图:
到这里,我们项目的架子基本上搭建完成了,剩下的只要根据自己的业务划分不同的模块,分别放入相应的数据层、容器层和组件层。
接下来的文章会分享一下react项目中如何解决自定义环境变量、开发测试生产打包以及多页面(html)的问题。
附上Demo的github地址:https://github.com/miguoer/react-sample