前言
最近一直在学习react技术栈,相关的理论和概念基本都了解了,之前也用reactjs写了几个demo,切身体会到了函数式编程和组件化开发的强大之处,但因各种主客观原因,事后没有对相关知识点进行梳理和总结,而且工作中也没用到,导致现在复习的时候生疏了,还需要花大部分时间重新理清需求和逻辑,做了很多重复性的工作,太得不偿失了。为了提高自己的学习效率,避免做一些无用的工作,我也决定以后(无论是工作还是学习)一定要养成定时总结的习惯,而且也要用文字记录下来,这样可以时常复习,理清逻辑,加深印象。另外,关于我个人的学习总结,如有不对的地方,欢迎批评指正,期待共同提高!不喜勿喷,谢谢!
第一章 页面搭建
目录结构
├── components | └──app.css//样式文件 ├── node_modules //依赖包 ├── static //静态文件 | └──index.html //入口html文件 | └──bundle.js //编译后的js文件 ├── index.js //主入口js文件 ├── package.json //项目所依赖的npm包 ├── webpack.config.js //webpack配置文件 └── yarn.lock //依赖或者更新包相关版本信息。这样可以解决同一个项目在不同机器上环境不一致的问题。
包管理文件 package.json
{ "name": "todolist","version": "1.0.0","description": "","main": "index.js","scripts": { "build": "webpack","start": "webpack-dev-server --line --hot","test": "echo \"Error: no test specified\" && exit 1" },"author": "www.icrazyman.cn","license": "ISC","devDependencies": { "babel-core": "^6.21.0","babel-loader": "^6.2.10","babel-preset-es2015": "^6.18.0","babel-preset-react": "^6.16.0","css-loader": "^0.28.4","react": "^15.4.1","react-dom": "^15.4.1","style-loader": "^0.18.2","webpack": "^1.14.0","webpack-dev-server": "^1.16.2" },"dependencies": { "node": "^6.11.1" } }
入口文件index.html
<!DOCTYPE html> <html lang="en"> <head> <Meta charset="UTF-8"> <title>Redux Todos Example</title> </head> <body> <div class="todoapp" id="root"></div> <script src="bundle.js"></script> </body> </html>
index.js
import第三方依赖包和css文件
import React from 'react' import { render } from 'react-dom' import './components/app.css' render( <div className='todo-Box'> <div className='todo-innerBox'> <div className='todo-tab'> <div className='todo-tab_item'> <a href='javascript:;'>全部任务</a> </div> <div className='todo-tab_item'> <a href='javascript:;'>待办任务</a> </div> <div className='todo-tab_item'> <a href='javascript:;'>已完成任务</a> </div> </div> <ul className='list-group'> <li className="todo-list_li"> <input type="checkBox" className="pull-left" value="on" /> aaa </li> <li className="todo-list_li"> <input type="checkBox" className="pull-left" value="on" /> bbb </li> <li className="todo-list_li"> <input type="checkBox" className="pull-left" value="on" /> ccc </li> </ul> <div> <form> <input placeholder='你想做点什么' className='todo-input' /> <button type='submit' className='todo-btn'>添加任务</button> </form> </div> </div> </div>,document.getElementById('root') )
webpack配置文件webpack.config.js
module.exports = { devtool: 'eval-source-map',//选择map来增强调试过程 entry: __dirname + '/index.js',//入口文件 output: { path: __dirname + '/static',//打包生成路径 filename: 'bundle.js' },module: { loaders: [{ test: /\.js$/,exclude: /node_modules/,loader: 'babel',query: { presets: ['es2015','react'] } },{ test: /\.css$/,loader: 'style-loader!css-loader' }] },devServer: { //热更新 contentBase: './static',historyApiFallback: true,inline: true,hot: true } }
打开终端运行yarn
或npm install
安装依赖,运行npm run build
编译,运行npm start
进行查看页面是否正常显示
小结:这个阶段只是把页面实现出来了,还没有实现任何逻辑。其中页面实现的步骤为:
1. 在index.html编写html结构和css样式 2. 把html结构提取到index.js组件中同时转换成jsx语法 3. 把css样式提取到app.css中
第二章 引入redux组件化
目录结构
├── actions | └──index.js//管理状态 ├── components | └──app.css//样式文件 | └──App.js//UI组件入口文件 | └──Link.js//UI组件 | └──Todo.js//UI组件 | └──Top.js//UI组件 ├── containers | └──VisibleTodoList.js//容器组件 | └──AddTodo.js//容器组件 | └──FilterLink.js//容器组件 ├── reducers | └──index.js//数据逻辑处理文件 ├── node_modules //依赖包 ├── static //静态文件 | └──index.html //入口html文件 | └──bundle.js //编译后的js文件 ├── index.js //主入口js文件 ├── package.json //项目所依赖的npm包 ├── webpack.config.js //webpack配置文件 └── yarn.lock //依赖或者更新包相关版本信息。这样可以解决同一个项目在不同机器上环境不一致的问题。
安装依赖包
此次新增react-redux和redux依赖包
{ "name": "todolist","author": "","react-redux": "^5.0.1","redux": "^3.6.0","dependencies": { "node": "^6.11.1" } }
index.js
此次利用redux管理整个项目的状态,并且把项目代码抽离成组件
//注意:import后不加{}代表引入的default,加了{}代表引入其中导出的一部分,用到了ES6的解构 //注意:import后不加{}代表引入的default,加了{}代表引入其中导出的一部分,用到了ES6的解构 import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore } from 'redux' //数据逻辑处理保存在reducers里 import todoApp from './reducers' import App from './components/App' //在顶层创建store管理整个项目的数据 const store = createStore(todoApp) console.log('9. root index start') //connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。 // 一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。 // React-Redux 提供Provider组件,可以让容器组件拿到state。 //Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。 render( <Provider store={store}> <App /> </Provider>,document.getElementById('root') ) console.log('9. root index end')
reducers/index.js
把数据逻辑处理的代码抽离成reducers
import { combineReducers } from 'redux' console.log('data flow:') console.log('1. reducers start') /* 传入旧的state和作用的action返回一个新state */ const todo = (state,action) => { console.log('data flow 4') switch(action.type) { case 'ADD_TODO': return { id: action.id,text: action.text,completed: false //新增默认为未完成 } case 'TOGGLE_TODO': if (state.id !== action.id) { return state } return Object.assign({},state,{completed: !state.completed}) default: return state } } const todos = (state = [],action) => { console.log('1.1 todos twice') switch(action.type) { case 'ADD_TODO': return [ ...state,todo(undefined,action) ] //todo(undefined,action) 新增一条记录时第一个参数state不存在 case 'TOGGLE_TODO': return state.map(t => todo(t,action)); default: return state; } } const visibilityFilter = (state = 'SHOW_ALL',action) => { console.log('1.2 visibilityFilter twice') switch (action.type) { case 'SET_VISIBILITY': return action.filter; default: return state; } } console.log('1. reducers end') export default combineReducers({ todos,visibilityFilter });
actions/index.js
在redux中,actions是触发store中数据更新的唯一来源,所以要写个actions利用dispatch方法来触发store中管理的状态更新
let nextTodoId = 0; console.log('2. actions start') // 添加 const addTodo = (text) => { // console.log('text:' + text) console.log('data flow 2') let id = nextTodoId ++ ; return { type: 'ADD_TODO',id: id,text } } // 顶部显示状态 const setVisibility = (filter) => { // console.log('filter:' + filter) return { type: 'SET_VISIBILITY',filter } } // 触发 const toggleTodo = (id) => { // console.log('id:' + id) return { type: 'TOGGLE_TODO',id } } export {addTodo,setVisibility,toggleTodo} console.log('2. actions end')
components/App.js
子组件的入口文件,负责对抽离出的子组件进行组合然后导出
import React from 'react' import Top from './Top' import VisibleTodoList from '../containers/VisibleTodoList' import AddTodo from '../containers/AddTodo' import './app.css' console.log('8. App.js start') const App = () => ( <div className='todo-Box'> <div className='todo-innerBox'> <Top /> <VisibleTodoList /> <AddTodo /> </div> </div> ) console.log('8. App.js end') export default App;
components/Top.js
TodoList顶部组件
import React from 'react' import { connect } from 'react-redux' import { setVisibility } from '../actions' import FilterLink from '../containers/FilterLink' console.log('4. Top.js start') const Top = () => ( <div className="todo-tab"> <FilterLink filter="SHOW_ALL"> 全部任务 </FilterLink> <FilterLink filter="SHOW_ACTIVE"> 待办任务 </FilterLink> <FilterLink filter="SHOW_COMPLETED"> 已完成任务 </FilterLink> </div> ); console.log('4. Top.js end') export default Top;
containers/FilterLink.js
过滤组件
import React from 'react' import { connect } from "react-redux" import { setVisibility } from "../actions" import Link from "../components/Link" //第二个参数表示组件自身的props //mapStateToProps()将state节点注入到与view相关的组件 const mapStateToProps = (state,ownProps) => { // console.log({state,ownProps}) return { active: ownProps.filter === state.visibilityFilter } } //mapDispatchToProps()将需要绑定的响应事件注入到组件上 const mapDispatchToProps = (dispatch,ownProps) => { return { onClick: () => { dispatch(setVisibility(ownProps.filter)) } } } //connent()函数生成容器组件 const FilterLink = connect( mapStateToProps,mapDispatchToProps )(Link) export default FilterLink;
components/Link.js
展示组件,显示顶部状态按钮
import React from 'react'; console.log('3. Link.js start') const Link = ({ active,children,onClick }) => { return ( <div className="todo-tab_item"> <a href = "#" style={{ color: active? '#f01414' : '#4d555d' }} onClick = { e => { e.preventDefault() onClick() } }> {children} </a> </div> ) } console.log('3. Link.js end') export default Link;
components/Todo.js
每一个Todo子组件
import React from 'react' console.log('5. Todo.js start') //UI 组件有以下几个特征。 // 只负责 UI 的呈现,不带有任何业务逻辑 // 没有状态(即不使用this.state这个变量) // 所有数据都由参数(this.props)提供 // 不使用任何 Redux 的 API // UI 组件又称为"纯组件",不含状态,纯粹由参数决定它的值 const Todo = ({ onClick,completed,text }) => ( <li className='todo-list_li' style={{textDecoration:completed ? "line-through" : "none"}}> <input type='checkBox' className='pull-left' value='on' onClick={onClick} defaultChecked={completed} /> {text} </li> ) console.log('5. Todo.js end') export default Todo;
containers/VisibleTodoList.js
TodoList列表组件
import React from 'react' import Todo from '../components/Todo' import { connect } from 'react-redux' import { toggleTodo } from '../actions' console.log('6. VisibleTodoList start') //负责管理数据和业务逻辑,不负责 UI 的呈现 // 带有内部状态 // 使用 Redux 的 API const getVisibleTodos = (todos,filter) => { console.log('9.2 getVisibleTodos') switch (filter) { case 'SHOW_ALL': return todos; case 'SHOW_COMPLETED': return todos.filter(t => t.completed === true)//已完成 case 'SHOW_ACTIVE': return todos.filter(t => t.completed === false)//未完成 default: throw new Error('Unknown filter: ' + filter) } } //合并redux的状态到react的props中 //将state映射到 UI 组件的参数(props) //mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。 //mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。 //使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染。 const mapStateToProps = (state,ownProps) => { console.log('9.1 mapStateToProps') // console.log(ownProps) // console.log(state) return { todos: getVisibleTodos(state.todos,state.visibilityFilter) } } //将用户对 UI 组件的操作映射成 Action //建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象 //如果mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数。 //mapDispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。 //如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。 const mapDispatchToProps = (dispatch) => { return { onTodoClick: (id) => { // console.log(id) dispatch(toggleTodo(id)) } } } const TodoList = ({ todos,onTodoClick }) => { console.log('9.3 TodoList') // console.log(todos);//todos数组 return ( <ul className='list-group'> {todos.map(todo => <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} /> )} </ul> ) } //React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。 //connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。 //connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。 const VisibleTodoList = connect( mapStateToProps,mapDispatchToProps )(TodoList) console.log('6. VisibleTodoList end') export default VisibleTodoList;
containers/AddTodo.js
添加Todo子组件
import React from 'react' import { connect } from 'react-redux' import { addTodo } from '../actions' console.log('7. AddTodo.js start') let AddTodo = ({ dispatch }) => { let input; return ( <div> <form onSubmit={ e => { console.log('data flow 1') e.preventDefault(); if (!input.value.trim()) { return } dispatch(addTodo(input.value)) input.value = '' } }> <input placeholder='你想做点什么' ref={r => input = r } className='todo-input' autoFocus={true}/> <button type='submit' className='todo-btn'> 添加任务 </button> </form> </div> ) } AddTodo = connect()(AddTodo)//把addTodo用redux的connect方法重新包装一下,使其可使用state中的数据 console.log('7. AddTodo.js end') export default AddTodo;
打开终端运行yarn
或npm install
安装依赖,运行npm run build
编译,运行npm start
进行查看页面和功能是否正常
小结:这个阶段把页面和功能实现出来了,初步把整个项目抽离成组件,也利用redux把代码业务逻辑处理、状态管理和状态分发管理分离出来了
总结:本次todolist的项目因时间关系中间断了两次,也算是“因祸得福”吧!我也把这个项目完整地复习了一遍。这个项目主要把redux的运用以及数据流了解了下,算是大概清楚怎么玩了,为什么说大概呢?一个是因为目前工作中没有使用,无法运用到实际项目中,一个就是最外层的index.js,不明白为什么console.log是最后打印出来的,也就意味这是最后加载这个文件的,这个文件我的理解不应该是整个项目js的主入口文件吗?为什么反而最后加载呢?不明白,先记下来吧,等以后用到redux了再有意识地研究一下。现在把自己做这个项目的详细步骤、个人理解以及查阅相关的资料整理出来了,不一定十分准确,如果有哪位大神有不同的意见欢迎批评指正!最新了解了react,很喜欢它的组件化开发,奈何公司用的却是JQ,所以在react里我其实还只是个新手,不喜勿喷啊!
原文链接:https://www.f2er.com/react/301592.html