最近周末有时间,想把加入前端一来一年时间对于react以及redux的理解记录下来,没有什么比一个产品更有说服力,在这里搭建一个简单的框架,供刚加入前端准备学习react的小白作为入门学习。
项目代码,稍后会有升级。
首先使用create-react-app要初始化一个脚手架。
然后安装一个依赖包yarn add babel-plugin-transform-decorators-legacy -D
,这种写法会将依赖包加载到package.json
的devDependencies
中,而不是dependencies
第一个是只用于开发环境,第二个要用于生产环境。所以生产环境中用不到的包就可以直接添加到第一个里面就好了。
下面是一共需要安装的包。我会尽量说一下每个的作用,尽量少的引入包,让系统更轻。
###依赖包
yarn add antd
安装antd
yarn add babel-plugin-transform-decorators-legacy -D
yarn add babel-preset-env -D
.babelrc中的配置需要
yarn add babel-plugin-transform-runtime -D
yarn add babel-plugin-import -D
配置后,可以引入模块,可以参考官网
yarn add babel-preset-stage-0 -D
yarn add babel-cli -D
这个好像不是必须加的,我移除了之后代码也可以运行。yarn add react-router-dom
安装了依赖包之后,在根目录下面新建一个.babelrc
来覆盖一些eslint的默认操作。
###新建.babelrc文件
{
"presets": [
"env", env参数囊括了es2015,2016,2017只写这一个就够了
"react", 这个参数一定要加,解析react的
"stage-0" 这是babel规范,没有这个,我知道的有import,export使用会发生异常。
],
"plugins": [
[
"transform-runtime", 这个插件,是添加一个小垫片,拒绝把常用函数添加到每个引用文件,避免重复
{
"helpers": false,
"polyfill": false,
"regenerator": true
}
],
"transform-decorators-legacy", 可以使用装饰器
[
"import", 这个是antd官网推荐的使用方式,不然antd无法正常模块按需引用
{
"libraryName": "antd",
"libraryDirectory": "es",
"style": "css"
}
]
]
}
这样可以保证编译的时候不报错了,但是你会发现被装饰器装饰的方法或者类下面会有小红线,这样可以在根目录下面新建jsconfig.json
###新建jsconfig.json文件
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "es2017"
},
"exclude": [
"node_modules",
"dist"
],
"include": [
"src",
"env",
"static"
]
}
总结上面这套流程可是费了不少功夫的,基本上是报错一个加一个,有的还找了半天错误在哪里,我居然还天真的以为create-react-app默认引用的包有antd,一个莫名其妙的错误找到最后居然是么有引入antd…
然后项目中需要react-redux 以及相关资源包。
"react-redux": "^5.0.6",
"react-router-redux": "^4.0.8",
"redux": "^3.7.2",
"redux-actions": "^2.2.1",
"redux-devtools-extension": "^2.13.2",
"redux-thunk": "^2.2.0",
以上就是所有的准备工作了,准备好之后就要开始干活了。提到redux,如果大家不是很清楚,可以参考这篇文章,这真是我读到redux入门最好的文章了。
然后,就要进入实战环节了,在src
目录下新建Action
,components
,containers
来存储网络请求/Action,组件以及页面容器。在Action目录下新建createFetchAction.js
作为访问网络的工具类,感兴趣的可以参考我的代码,我做了很详细的注释。
然后看一下store里面的代码:
const composeEnhancers = composeWithDevTools({
//为了redux dev tools 服务
// options like actionSanitizer, stateSanitizer
});
const middleware = [thunk]; // 这里是加载的一个中间件,可以加入很多,也为实现功能提供了无限的扩展性和可能性。
const store = createStore(combineReducers({routering: routerReducer}), // 这个是引入的默认路由
{},
composeEnhancers(
applyMiddleware(...middleware)
));
store.appReducers = {} //全局的reducer
export const updateReducer = (key,reducer) => {
if (Object.hasOwnProperty.call(store.appReducers, key)) return;
store.appReducers[key] = reducer; // 每调用方法,可以将当前reducer加入全局
// 其中的routerReducer是引入的初始化状态,也可以自己本地定义一个,在项目启动的时候就加入进来。
store.replaceReducer(combineReducers({routering: routerReducer ,...store.appReducers}));
}
export default store;
项目的入口就是App.js
文件,在这个文件中要配置路由以及路由对应的要显示的组件。
async componentDidMount() {
Object.keys(reducers).forEach(name=>{
// 在项目运行开始引入全部的reducer,然后加载时全部添加到`appReducers`里面
updateReducer(name,reducers[name]);
});
}
render() {
return (
<Provider store={store}> 通过provider全局引入 store
<BrowserRouter> 监听路由变化,并且加载对应组件
<div style={{ width: '100%'}}>
<Switch> Switch中的组件只能同时加载一个,顺序从上向下依次判断
<Route path='/login' component={APP.LOGIN} />
<Route path='/page404' render={() => {
return (<h1>404 not found</h1>); // 这个组件过于简单,不新建文件了
}} />
<Redirect from='/' to='/page404' /> 重定向url
</Switch>
</div>
</BrowserRouter>
</Provider>
);
}
上面看到了,引入了Login这个组件,所以,在containers
中新建Login.js
文件.这个文件中会有登录验证的代码,暂时先不用管,我自己在本地搭建了一套Node
的服务器,这个过程稍后也会加入分项列表。
#####再讨论一下Action的创建:
导出的index.js我就不多说了,这里介绍一下login
文件夹下面的两个文件:
index.js
[combineActions(...getValuesFromObj(actions))]:(state = {},action)=>{
return {
...state,...loginReducer(state,action) 这个...loginReducer,包含了多个从login.js中导出的reducer,每次当从页面中调用同名的action,
比如setLoginData就会对应到这边来,以调用setLoginData来说,就是返回当前的state和loginData这两个数据的合体。
这也是redux的核心里面,每次只更新,不能修改。
}
}
login.js
const setLoginData = createAction('登录action');
const setLoginData1 = createAction('测试action');
const getAllEmployeeOk = createAction('得到所有用户');
//一下对本地的请求可以换成任何url,比如baidu等均可,调用成功之后会回调getAllEmployeeOK函数。
const getAllEmployee = createAsyncAction('http://localhost:8080/tabledata','GET',getAllEmployeeOk);
export const actions = {
setLoginData,
setLoginData1,
getAllEmployee,
getAllEmployeeOk
}
export default handleActions({
[setLoginData]: (state, {payload}) => ({
loginData: payload
}),
[setLoginData1]: (state, {payload}) => ({
loginData1: payload
}),
[getAllEmployeeOk]:(state,{payload})=>{
return{
employee:payload
}
}
},{});
login.js
是对函数进行定义并导出,index.js
中的操作是对login.js
导出的内容进一步拆分,比如login
算一个,未创建的register
也可以算一个。在这里利用handleActions
导出了诸多actions
,这里的操作相当于注册这些reducer
,当在页面方法中调用该action的时候,就可以根据规则更新redux的值了【最终一个模块的是汇总到updateReducer中】。
那个getValuesFormObj本来官方提供的有ActionsMap提供使用,但是为了说明他的本质,我就自己写了个阉割版的。
这个组件就只是展示了用户名和密码的输入框,然后在componentDidMount
和点击事件中对redux做了个小测试。
@connect(
state => { 这个state是全局的state,也可以在这里进行一下过滤表示只想要一部分的store值。
return state; 这里返回的state会被映射到Props,可以通过this.props访问到
} 如果想访问一部分,可以 return state.login {如果有这个分支的话}
,
dispatch => ({
//bindActionCreators这个函数为通过createAction方式建立的action提供dispatch参数,要知道没有dispatch就无法调用action
同时connect帮助我们,将所有的action[还有网络访问相关的]加入props中
bindedactions: bindActionCreators({ ...actions }, dispatch),
})
)
componentDidMount () {
const { setLoginData1 } = this.props.bindedactions;
setLoginData1('123'); 调用之后,redux的store中就多了一条记录,可以打印this.props确认。
}
到这里,一个基本的架构就搭建起来了,后续我会把后台代码也一并总结上来。
如果有疑问或者认为我写的有缺陷的,希望能提出宝贵的建议,不胜感激!