Nav logo
120
发现
关注
消息 4

搜索
从零开始搭建一个react项目
96 瘦人假噜噜
2017.04.23 23:29* 字数 6330 阅读 32892评论 31喜欢 36
项目地址

从头开始建立一个React App - 项目基本配置
npm init 生成 package.json 文件.
安装各种需要的依赖:
npm install --save react - 安装React.
npm install --save react-dom 安装React Dom,这个包是用来处理virtual DOM。这里提一下用React Native的话,这里就是安装react-native。
npm install --save-dev webpack - 安装Webpack, 现在最流行的模块打包工具.
npm install --save-dev webpack-dev-server - webpack官网出的一个小型express服务器,主要特性是支持热加载.
npm install --save-dev babel-core - 安装Babel, 可以把ES6转换为ES5,注意Babel最新的V6版本分为babel-cli和babel-core两个模块,这里只需要用babel-cor即可。
安装其他的babel依赖(babel真心是一个全家桶,具体的介绍去官网看吧..我后面再总结,这里反正全装上就是了):
npm install --save babel-polyfill - Babel includes a polyfill that includes a custom regenerator runtime and core.js. This will emulate a full ES6 environment
npm install --save-dev babel-loader - webpack中需要用到的loader.
npm install --save babel-runtime - Babel transform runtime 插件的依赖.
npm install --save-dev babel-plugin-transform-runtime - Externalise references to helpers and builtins, automatically polyfilling your code without polluting globals.
npm install --save-dev babel-preset-es2015 - Babel preset for all es2015 plugins.
npm install --save-dev babel-preset-react - Strip flow types and transform JSX into createElement calls.
npm install --save-dev babel-preset-stage-2 - All you need to use stage 2 (and greater) plugins (experimental javascript).
打开 package.json 然后添加下面的scripts:
"scripts": {
"start": "webpack-dev-server --hot --inline --colors --content-base ./build",
"build": "webpack --progress --colors"
}
命令行输入 npm start 将要启动webpack dev server.

命令行输入 npm build 将会进行生产环境打包.

启动webpack
Webpack是我们的打包工具,在我们的开发环境中具体很重要的作用,具有很多非常便捷的特性,尤其是热加载hot reloading. webpack.config.js 是如下所示的webpack的配置文件. 随着app的不断变化,配置文件也会不断的更新,这里我们就用默认的webpack.config.js来命名这个配置文件,假如你用别的名字比如webpack.config.prod.js那么上面的脚本build就需要相应的改变指定相应的配置文件名字:"build": "webpack webpack.config.prod.js --progress --colors"

var webpack = require('webpack');
module.exports = {
entry: './src/app.js',
output: {
path: __dirname + '/build',
filename: "bundle.js"
},
module: {
rules: [{
test: /.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
plugins: ['transform-runtime'],
presets: ['es2015', 'react', 'stage-2']
}
}, {
test: /.css$/,
loader: "style-loader!css-loader"
}]
}
};

OK,我们项目的基本配置终于完成了,是时候开始写Reac代码了.
React 基础 - 建立你的第一个Component
在上面的项目的基本配置基础上,我们开始书写React的第一个组件来熟悉React的写法与组件思想。

首先我们在项目根目录中新建一个 index.html 文件。 在这个基础工程中, 我们使用bootstrap的样式,直接引入一个cdn即可. 然后添加一个html标签

,我们的app就会注入到这个div中。 最后再引入 ,这是最后打包生成的js代码。

以下是完整的代码:

<!DOCTYPE html>

Document

建立一个新的文件夹 src. 我们app的大部分代码都将放在这个文件夹里面。在 src 中建立 app.js,作为React App的根组件, 其他所有的组件都会注入到这个跟组件中。

首先我们需要导入react,现在都已经用ES6的语法, import React from 'react'; , 然后我们要引入react-dom. 这里面有react中最重要的一个虚拟dom的概念.引入代码:import ReactDOM from 'react-dom';

现在需要引入的依赖都已经完毕我们可以写第一个组件了:

class App extends React.Component {
render(){ // Every react component has a render method.
return( // Every render method returns jsx. Jsx looks like HTML, but it's actually javascript and functions a lot like xml, with self closing tags requiring the / within the tag in order to work propperly


Hello World

);
}
}
注意这里"Hello World"写在 div中. 所有的jsx代码都需要写在一个父div中.

最后我们需要把我们写好的组件render给Dom,这里就需要用到 ReactDOM.render 方法.

在 App.js 的下面添加: ReactDOM.render(, document.getElementById('app'));

第一个参数就是我们App的根组件, 写作的形式. 第二个参数就是我们的APP将要主要的DOM元素. 在这个项目中,就是我们在index中写的id为app的 div标签。

Ok,我们的APP结构已经出来了,经典的hello world已经实现。马上我们就在这个基础上再实现经典的todo app。大致的原型就有一个输入框用来输入代办事项然后添加到事件列表中。事件列表中每一个代办事项被点击就会标注一条删除线表示完成,点击后面的删除按钮则会将其从列表中删除。通过完成这个APP的过程你将学会一个完整的react app的所有的基本构建块。
生命周期方法和两种形式的组件构建
我们从一些小的模块开始起步.一个组件component就是一个react app的构件块. 有两种形式的组件: 类组件(Class)和函数型组件(Functional). 在这个项目中,这两种形式的组件我们都会使用, 并且使用生命周期的钩子,同时也会使用state和props两个react中重要的属性。

首先在 src文件夹中新建components 文件夹,你的文件结构就是这样 ~/src/components。

然后在components中新建文件 ToDoApp.js。 对于所有的react组件我们都需要在头部引入reactimport React from 'react';。

下面我们写一个类组件. 所有的class 组件有一个render方法用来返回jsx。

ToDoApp的class就如下所示:

class ToDoApp extends React.Component {
render() {
return (

To Do App

);
}
}
为了将这个组件注入到我们的APP中, 首先我们需要输出它。 在这个组件代码底部添加 export default ToDoApp;。

然后在app.js顶部我们添加 import ToDoApp from '.components/ToDoApp'; 导入组件用来代替 Hello World 。 render中替换为新的jsx代码 半闭合类型的标签即可。

然后在浏览器中你就可以看到"To Do App" 代替了原来的 "Hello World"!这样我们就完成了将第一个子组件嵌入到根组件之中了,这就是构建react app的常规模式。下面继续完善我们的组件。

返回到ToDoApp 中来构建我们的第一个代办事项列表。首先我们使用bootstrap来构建比较方便且美观。 用下面的jsx替换当前render方法中 return 中的jsx:

My To Do App


List goes here.

现在打开浏览器, 你将会看到一个标题 "My To Do App" 下面跟随一个bootstrap的panel组件里面写有 "List Goes Here",我们将在这个地方构建列表。 那么我们如何将数据存储在我们的列表中呢? 答案就是使用 state. 每一个类组件都有 state 属性,可以通过 this.state在组件任何位置获取并且用 this.setState({ key: "value" })这种方法来更新状态。但是除非必要我们比较少使用state,这里暂时先使用作为了解,后期会使用redux来管理状态。

在ToDoApp中我们可以使用许多生命周期方法的钩子, 其中一个就是componentWillMount。 这个方法的执行是在页面加载并且render方法之前。可以在其中获取列表数据,在我们的APP中直接用一个虚拟的数组提供。(值得注意的是componentWillMount会引起很多小问题,因此真实项目中尽量不要使用,而是应该用componentDidMount)。
在 ToDoApp中 render 方法之前添加:

componentWillMount(){ // run before the render method
this.setState({ // add an array of strings to state.
list: ['thing1', 'thing2', 'thing3']
})
};
现在我们获取了一个虚拟列表,需要重点注意的就是react依赖于state和props,只有当state和props改变的时候react组件才会刷新。

现在我们添加列表到这个view里,这里不是直接简单的在里面修改jsx,而是再创建一个新的组件来构建列表,这次我们学习使用函数型组件,需要注意的是函数型组件没有生命周期方法和state属性,它仅仅是一个返回jsx的函数,并且参数是props。

那么props到底是什么呢?props是从父组件传递进子组件的数据的名字,这是一个很重要的概念,也是react app数据传递的最典型与最推荐的方法。通常我们将数据保持在app的顶端组件,通过组件让数据流下来保证APP的精确运行。这些数据和props的一些处理可能会影响APP的运行,但是假如你按照这个课程的实践流程来做,这些影响都会很小。

再新建一个components文件夹并在其中新建一个List.js作为我们要创建的函数型组件。用const来新建一个函数,参数名字写作props。
函数形式如下所示:

const List = (props) => { // we're using an arrow function and const variable type, a ES6 features

return (


I'm a list!!!

)
};

export default List;
在 ToDoApp.js引入 List用List 组件替换 List goes here.,写法为 .现在在浏览器中就可以看到"I'm a list!!!"

现在我们来把这个变成真实的列表,首先就需要通过props传递数据,我们把这个从state中获取的数据list通过命名为listItems的props传递,写作: ,现在 List 已经通过props获取了 ToDoApp中的数据。

然后在 List 组件中我们需要render一个列表,先用下面的jsx代码代替:

  • { list // this is a variable we'll define next }

注意这个大括号,js可以在这里面执行并将返回添加到view里。首先我们定义一个列表变量:

const list = props.listItems.map((el, i)=>(
// All where doing here is getting the items listItems prop
// (which is stored in the state of the parent component)
// which is an array, and we're running the .map method
// which returns a new array of list items. The key attribute is
// required, and must be unique.

el

));
完整的组件如下:

import React from 'react';

const List = (props) => {

const list = props.listItems.map((el, i)=>(

el

));

return (

  • {
    list
    }

)
};

export default List;
现在打开浏览器就可以看到一列列表了。接下来就是给我们的项目加入功能了,包括添加新的事项,标注事项完成和删除列表中事项。

给APP添加功能
1.函数型组件
首先我们需要添加一个input元素以便可以输入代办事项。因此我们在components文件夹中新建一个Input.js,然后在其中创建并输出一个名叫Input的函数型组件。
把下面的jsx代码粘贴到你的函数型组件return之中:

Email address Add Item
  1. Input
    现在我们的jsx没有做任何特殊的事情,仅仅是一个基本的html视图,不过我们先测试一下把其导入到ToDoApp.js,形式就是。

这时候会发现一个输入框和按钮的视图,这个组件的静态视图已经写好了,下面就需要添加功能了。

  1. Props
    首先我们需要做的是如何获取输入框的值,因为这个输入框的值需要在其他组件中获取,所以我们并不想要在Input组件中来处理这个数据存储。事实上,在子组件中存储数据在任何时候都是不推荐的,我们应该将数据存储在app的顶端组件并且通过props传递下来。

另一个需要记住的是即使我们目前把数据存储在了上层的 ToDoApp 组件,后期还是会用redux来代替来处理整个app的数据。这里先仅仅使用react的state来实现。

ok,我们在ToDoApp的 componentWillMount的setState中新增一个newToDo属性用来存储输入框的值。

componentWillMount(){
this.setState({
list: ['thing1', 'thing2', 'thing3'],
newToDo: 'test'
})
};
同样的就可以通过在上通过props传递下去。

  1. 解构(Destructuring)
    在Input.js中我们通过参数props可以获得上级组件传递下来的值, 但是还可以用ES6的新特性解构来作为参数,这样看起来更加酷!

把Input组件的props参数修改为({ value })这样的参数形式,这样可以把props这个对象参数解构为一个个键值对。直接看个小例子来就很明白了:

var props = {
name: 'hector',
age: 21
}

function log(props){
console.log(props.name);
console.log(props.age);
}

log(props);
is the same as this:

let props = {
name: 'hector',
age: 21
}

log = ({name, age}) => {
console.log(name);
console.log(age);
}

log(props);

  1. setState
    上面的newToDo仅仅是添加了一个state用来存储输入框的值,给定一个值,输入框就会显示,明显还不是我们要的效果,我们需要做的是基于输入框的值的改变来动态改变这个state。

为了实现这个功能,我们需要再添加一个onChange方法同样利用props传进Input组件: onChange={onChange}, 然后解构参数就是({ onChange, value })。

然后在 ToDoApp.js的componentWillMount 添加一个新的方法 onInputChange。这个方法有一个参数event, 它将要捕获用户在输入框输入的值。

onInputChange = (event) => {
this.setState({ newToDo: event.target.value}); // updates state to new value when user changes the input value
};

  1. 添加新列表事项
    现在需要向列表中添加新的事项,也就是在提交后能把输入框的值存储并显示到列表中。我们需要再新建一个onInputSubmit的方法,参数同样是event,函数体内首先需要写 event.preventDefault(),然后用 setState 方法把新事项添加到列表数组中,但是,一定要注意我们的state应该是immutable的,这是react中必须遵循的一个准则,这样才能保证对比性与可靠性。

为了实现这个功能, 需要用到this.setState 回调函数,参数为previousState:

this.setState((previousState)=>({
list: previousState.list.push(previousState.newToDo)
}))
正如我上面的描述,最开始写state的时候很多人都会犯这样的错误,直接用push这样的方法,修改了state,这样就不算immutable的,我们一定要保证绝不直接修改原state。

这里又可以用到ES6中的新特性了,扩展操作符,它通过遍历旧数组返回一个新数组,使旧的数组保持原样,这样我们就把事项添加到列表数组末尾:

this.setState((previousState)=>({
list: [...previousState.list, previousState.newToDo ], // the spread opperator is called by using the ... preceding the array
}));
在提交添加新事项的同时,需要将newToDo重置为'':

this.setState((previousState)=>({
list: [...previousState.list, previousState.newToDo ],
newToDo: ''
}));

  1. 划掉事项
    是时候添加划掉事项的功能了。为了实现这个功能需要添加一个新的属性用来标注是否需要划掉,因此需要改变原来的数组为一个对象数组,每一个事项都是一个对象,一个key为item表示原来的事项内容,一个key为done用布尔值来表示是否划掉。 然后先把原来的onInputSubmit方法修改,同样要注意immutable,使用扩展操作符如下:

onInputSubmit = (event) => {
event.preventDefault();
this.setState((previousState)=>({
list: [...previousState.list, {item: previousState.newToDo, done: false }], // notice the change here
newToDo: ''
}));
};
属性done添加完成后就需要新增一个方法当点击事项时候来改变这个值:

onListItemClick = (i) => { // takes the index of the element to be updated
this.setState((previousState)=>({
list: [
...previousState.list.slice(0, i), // slice returns a new array without modifying the existing array. Takes everything up to, but not including, the index passed in.
Object.assign({}, previousState.list[i], {done: !previousState.list[i].done}), // Object.assign is a new ES6 feature that creates a new object based on the first param (in this case an empty object). Other objects can be passed in and will be added to the first object without being modified.
...previousState.list.slice(i+1) // takes everything after the index passed in and adds it to the array.
]
}))
};
然后把这个方法通过props传递给List 组件,这里就没有使用解构参数传递,用来和Input的做对比。因为这个函数需要一个参数就是当前列表的序列号,但是肯定不能直接call这个函数否则会报错,因此使用bind方法,出入i参数:

onClick={props.onClick.bind(null, i)}
当然还有另一种方法:

onClick={() => props.onClick(i)}
然后在事项内容的span标签上添加 onClick 方法,改变当前事项的done值后,在通过判断此布尔值来进行样式的修改添加或者划掉删除线。

<span
style={
el.done
? {textDecoration: 'line-through', fontSize: '20px'}
: {textDecoration: 'none', fontSize: '20px'}
}
onClick={props.onClick.bind(null, i)}

  1. 删除事项
    最后我们在添加删除事项的功能,这个和划掉事项非常相似,我们只需要新增一个删除按钮,然后再新增一个方法修改列表,具体代码如下:

x

deleteListItem = (i) => {
this.setState((previousState)=>({ // using previous state again
list: [
...previousState.list.slice(0, i), // again with the slice method
...previousState.list.slice(i+1) // the only diffence here is we're leaving out the clicked element
]
}))
};
把deleteListItem 方法传递到列表组件中然后在删除按钮上绑定即可,仿照上一个自己写一下就好。

现在我们有一个完整功能的APP了,是不是感觉很cool,这个就是不用redux时候的形态了,但是你会发现当状态越来越复杂时候很繁琐,因此我们下面就要介绍redux来管理状态了。

迁移到redux的准备工作
截至目前我们已经学会如何用webpack和babel搭建react应用,构建类组件和函数型组件并处理state,添加功能。然而这只是基本满足一个小型应用的需求,随着app的增长,处理数据和行为会越来越吃力,这就是要引入redux的必要性。

那么redux如何处理数据?首先,redux给你的app一个单一的state对象,与flux等根据view来划分为多个state对象正好相反。你可能会有疑问,一个单一的对象来处理一个复杂的app岂不是非常复杂?redux采用的方法是把数据处理分为reducer functions、action creators和actions然后组合在一起工作流线型的处理数据。

  1. 首先安装必须的依赖
    首先安装 redux and react-redux

npm install --save redux
npm install --save react-redux
然后安装 redux middleware,这里就先安装 redux-logger,它的功能是帮助我们开发。

npm install --save redux-logger
还有一些常用的中间件,比如 redux-thunk and redux-promise, 但是在我们的这个项目中暂时先不需要,可以自行去github了解。

  1. 构建
    使用redux构建react应用一般都有一个标准的模板,可能不同模板形式上有区别,但是思想都是一样的,下面就先按照一种文件结构来构建。

首先我们在src中新建一个文件夹redux,然后在其中新建一个文件configureStore.js,添加以下代码:

import { createStore, applyMiddleware, combineReducers } from 'redux';
import createLogger from 'redux-logger';
createStore 是由redux提供的用来初始化store的函数, applyMiddleware是用来添加我们需要的中间件的。

combineReducers 用来把多个reducers合并为一个单一实体。

createLogger 就是我们这里唯一使用的一个中间件,可以console出每一个action后数据的详细处理过程,给调试带来了很大方便。

然后添加下面代码:

const loggerMiddleware = createLogger(); // initialize logger

const createStoreWithMiddleware = applyMiddleware( loggerMiddleware)(createStore); // apply logger to redux
这里暂时没有完成,需要后面的模块写完了再导入到这里继续来完成。

  1. 模块Modules
    在 src/redux/ 新建一个文件夹 modules。在这个文件夹中我们将存放所有的reducers,action creators和constants。这里我们使用的redux组织结构叫做ducks,思想就是把相关的reducers,action creators和constants都放在一个单独的文件中,而不是分开放在多个文件中,这样修改一个功能时候直接在一个文件中修改就可以。

在 modules 文件中新建 'toDoApp.js',注意这里的命名是依据容器组件的名字来命名,这个也是规范,容易管理代码。

现在我们可以开始创建initial state和 reducer function,这其实非常简单,state就是一个js对象,reducer就是js的switch语句:

const initialState = {}; //The initial state of this reducer (will be combined with the states of other reducers as your app grows)

export default function reducer(state = initialState, action){ // a function that has two parameters, state (which is initialized as our initialState obj), and action, which we'll cover soon.
switch (action.type){
default:
return state;
}
}

  1. 完善Store
    现在我们已经完成了第一个reducer,可以将其添加到 configureStore.js 中去了, 导入: import toDoApp from './modules/toDoApp';

然后用combineReducers 来组合当前的reducer,因为未来会有更多的模块加入。

const reducer = combineReducers({
toDoApp
});
最后在底部加入下面完整的代码:

const configureStore = (initialState) => createStoreWithMiddleware(reducer, initialState);
export default configureStore;
Cool. We're done here.

  1. Connect
    现在我们已经有了一个reducer,那么怎么和app建立联系呢?这需要两步工作。

前面已经讲过类组件和函数型组件,有时候也可以称为smart components和dumb components,这里我们新增一种容器组件,顾名思义,这种组件就是作为一个容器用来给组件提供actions和state。

下面来创建第一个容器组件,首先在 /src/ 下新增一个文件夹containers,然后再其下面新建一个文件 toDoAppContainer.js。
在文件顶部首先导入 connect 用来将容器和组件联系在一起,

import { connect } from 'react-redux';
import ToDoApp from '../components/ToDoApp.js'
connect 这个函数被调用两次, 第一次是两个回调函数: mapStateToProps and mapDispatchToProps。 第二次是把state和dispatch传入组件的时候。这里的dispatch又是什么呢?

当我们需要在redux中发生某些行为时候,就需要调用dispatch函数传递一个action然后调用reducer这一套流程。因为我们还没有编写具体的行为,这里就暂时空白,后面再补,代码形式如下:

function mapStateToProps(state) {
return {
toDoApp: state.toDoApp // gives our component access to state through props.toDoApp
}
}

function mapDispatchToProps(dispatch) {
return {}; // here we'll soon be mapping actions to props
}
然后在底部添加:

export default connect(
mapStateToProps,
mapDispatchToProps
)(ToDoApp);
Provider
redux的基本工作已经完成,最后一步就是返回到app.js 文件, 首先我们不再需要导入 ToDoApp 组件,而是用容器组件ToDoAppContainer来替代,然后需要导入 configureStore 函数和 Provider,在头部添加代码:

import { Provider } from 'react-redux';
import ToDoAppContainer from './containers/ToDoAppContainer';
import configureStore from './redux/configureStore';
configureStore is the function we created that takes our combined reducers and our redux middleware and mashes them all together. Let's intialize that with the following line:

const store = configureStore();
然后return的jsx中同样需要把ToDoApp 改为 ToDoAppContainer,然后需要用Provider 组件将其包裹,它的作用就是将整个app的state传递给它所包裹的容器,从而使容器组件可以获取这些state。

// we pass the store through to Provider with props

现在整个redux的基本结构已经搭建起来,下一步就可以把整个行为逻辑代码补充进去就可以了。

Redux Actions 和 Reducers
搭建起redux的基本结构后,就可以填充redux的元素了,简单来说我们只需要记住四个概念, Types, Actions, Action Creators, and Reducers。然后把这些元素用ducks的文件组织结构组织起来就可以了。

Ducks
规则
在module中我们需要遵循下面的代码风格和命名方式:

须用 export default 输出名为 reducer()的函数
须用 export 输出 函数形式的action creators
须用 npm-module-or-app/reducer/ACTION_TYPE
的命名形式来命名action types,因为到后期很多reducer,不同的人协同工作难免会出现命名重复,这样子加上app和模块的前缀的话就不会出现命名冲突的问题。
须用大写的蛇形方式UPPER_SNAKE_CASE来命名action types。
Types
这个types就是上面第三条中需要按照ducks的规范命名的常量名字,将其写在文件的顶部,当action 触发时候会传递给reducer,reducer的switch语句会根据这个type来进行相应的数据处理。

const ADD_ITEM = 'my-app/toDoApp/ADD_ITEM';
const DELETE_ITEM = 'my-app/toDoApp/DELETE_ITEM';
Actions
Actions 就是一个至少包含type的简单的js对象,同时可以包含数据以便传递给reducer。当用户在页面上触发了某种行为,一个aciton creator将会发送aciton给reducer做数据处理。

action示例如下:

{ type: ADD_ITEM, item: 'Adding this item' }
{ type: DELETE_ITEM, index: 1 }
{ type: POP_ITEM }
Action Creators
Action creators 是创建acitons并传递给reducer的函数,它通常返回一个action对象,有时候借用thunk这样的中间件也可以返回dispatch多个actions,在我们的app中为了简化暂时不涉及这个模式。

function addItem(item){
return {
type: ADD_ITEM,
item // this is new ES6 shorthand for when the key is the same as a variable or perameter within the scope of the object. It's the same as item: item
}
}
Reducers
reducer是唯一可以触碰store的元素,初始值为initialState,形式上就是一个简单的switch语句,但是注意不能直接改变state,因为state是immutable。也就是说我们不能直接使用.pop or .push这些方法操作数组。

下面是示例代码:

const initialState = {
list: []
};

export default function reducer(state = initialState, action){
switch (action.type){
case ADD_ITEM:
return Object.assign(
{},
state,
{ list: [...state.list, action.item]} // here we see object.assign again, and we're returning a new state built from the old state without directly manipulating it
)
default:
return state;
}
}
概念已经介绍完毕,下面开始将原来的功能逻辑用redux重写。

  1. Initial state
    首先我们在 src/redux/modules/toDoApp中声明initialState。

const initialState = {
list: [{item: 'test', done: false}] // just added this to test that state is being passed down propperly,
newToDo: ''
};

export default function reducer(state = initialState, action){
switch (action.type){
default:
return state;
}
}
现在在 ToDoApp.js的 render() 方法中return之前添加console.log(this.props) 会打印出下面的对象:

toDoApp: Object
list: Array[1]
0: "test"
length: 1
proto: Array[0]
proto: Object
proto: Object
测试通过,我们就可以传递这些数据给子组件了,这里就可以把原来List组件的 listItems prop和Input的value prop替换掉了。

这里只是替换掉了数据,下面还需要把action也替换。

  1. Input action
    这个过程就是把我们原来在ToDoApp 组件的行为逻辑全部迁移到redux文件夹下的 toDoApp module中去。

const INPUT_CHANGED = 'INPUT_CHANGED';

export function inputChange(newToDo){
return {
type: INPUT_CHANGED,
newToDo
}
}
然后在reducer的switch中新增如下处理:

case INPUT_CHANGED:
return Object.assign(
{},
state,
{newToDo: action.value}
);
在 toDoAppContainer.js 的 mapDispatchToProps 函数就需要返回相应的action,首先导入 inputChange, 具体代码如下:

import { connect } from 'react-redux';
import ToDoApp from '../components/ToDoApp.js'
import {
inputChange
} from '../redux/modules/toDoApp'; // we added this

function mapStateToProps(state) {
return {
toDoApp: state.toDoApp // gives our component access to state through props.toDoApp
}
}

function mapDispatchToProps(dispatch) {
return {
inputChange: (value) => dispatch(inputChange(value)) // we added this
};
}

export default connect(
mapStateToProps,
mapDispatchToProps
)(ToDoApp);
这样state和action都传递给了toDoApp然后再通过props传递给子组件就可以使用了,具体都可以看项目最终代码。

  1. 其他 actions
    其他acitons的代码模式跟上面的基本一样,这里不在赘述。

总结
到这里一个使用webpack打包的react+redux(ducks)的基本应用模型就出来了,虽然简单但是是我们进行更复杂项目的基础,并且有了这些基础后面的路程将会顺畅多了,一起加入react的大家庭吧。

小礼物走一走,来简书关注我

前端 © 著作权归作者所有 举报文章
96 瘦人假噜噜
写了 12779 字,被 40 人关注,获得了 49 个喜欢

博客园日薄西山,简书风格不错,迁移到此希望能认识些朋友共同进步。
更多分享

写下你的评论...
31条评论 只看作者 按时间倒序按时间正序
chendidi
17楼 · 2018.02.01 10:51
1:"start": "webpack-dev-server --hot --inline --colors --content-base ./build",把./build去掉;
2: import createLogger from 'redux-logger' 改成------》import { createLogger } from 'redux-logger' ,
其他正常按照上文,就可以弄出个demo了,很基础,很适合刚开始结合redux/react/webpack搭建一个react项目。
谢谢博主的文章
我弄的代码保存我的github项目React-Instance的ConstructedProject,有兴趣可以看看

1人赞 回复
qing_69cf
16楼 · 2018.01.17 00:38
在解构和setState那就看不懂了,到底是在哪个组件加onChange方法,是在Input.js还是在ToDoApp.js加?能不能把代码贴出来?求解答!

赞 回复
这一步我做出来了,给你发一下代码?

2018.03.04 11:23 回复
作者这里没写清楚,他的意思其实就是你在外层定义一个方法,然后把这个方法穿进组件,在组件里的input标签上绑定一个原生onChange = {onChange},花括号里的是外面传进来的

2018.05.20 12:51 回复
添加新评论
小明文学
15楼 · 2018.01.05 17:29
用函数创建的List报未定义错误,改成render()写法就好了,但是因为新手,后面导入列表参数的地方不太会改,还望指点一下

赞 回复
找到了,忘引入react了

2018.01.05 17:46 回复
@小明文学 我也是遇到这个问题

2018.01.16 21:29 回复
添加新评论
方物_ddd6
14楼 · 2018.01.04 10:55
onListItemClick划线 这个方法里面写的代码没有看懂,各种slice是为什么呀,能不能解答一下.

赞 回复
喔 知道了 是es6的新语法

2018.01.05 10:00 回复
添加新评论
方物_ddd6
13楼 · 2018.01.01 17:56
一步一步下来,启动成功了,然后在webpack.config.js的output里,写这行,publicPath:"/build/",就能自动编译,刷新浏览器了。

赞 回复
伊人依伊
12楼 · 2017.12.27 17:30
好赞?,按流程来小白已经成功搭建第一个项目。中间 devServer 配置中color和process 已经没有了,配置时拿掉就好了,谢谢分享

赞 回复
为什么我npm start,页面没东西啊,页面只显示了:Cannot GET /。hello world都没有,这是为什么呢

2018.01.16 20:11 回复
添加新评论
只此一次
11楼 · 2017.11.01 14:24
components文件夹下,无论如何都不能 建新的子组件,否则就报错

赞 回复
13669291126
10楼 · 2017.10.19 15:18
循环列表是否应该为:
const list = props.listItems.map((el, index) => {
return

{el}
});

赞 回复
el -> {el}?

2017.11.23 11:14 回复
添加新评论
钱罗罗_
9楼 · 2017.09.28 16:45
"start": "webpack-dev-server --hot --inline --colors --content-base ./build",
跑不出来的,可以将这里的./build删掉,原来端口读出来的是build下的文件。

赞 回复
嗯 按你说改了,真的对了

2018.01.01 17:54 回复
但是页面什么都不显示?连Hello World都没有?

2018.01.08 15:37 回复
@我就叫认真 没有配webpack.config.js吧,配上就好了

2018.01.16 20:15 回复
添加新评论
倔强的卤蛋
8楼 · 2017.08.31 17:02
的确步骤有问题,没写写入webpack.config.js文件

赞 回复
雨中漫步的北极熊
7楼 · 2017.08.24 16:43
项目写的给自己看的,都跑不起来

赞 回复
I在简书
6楼 · 2017.08.21 15:13
初步判断, 这篇文章应该是翻译国外大神的, 有一些版本变动, 不过注意看错误信息, 应该能解决;
比如: import createLogger from 'redux-logger' 应该改成
import {createLogger} from 'redux-logger'

赞 回复
一只好奇的茂
5楼 · 2017.08.20 12:55
工程跑不起来呀。

赞 回复
whd_8ef1
4楼 · 2017.08.09 11:13
http://www.jqhtml.com/6435.html

赞 回复
luj1n
3楼 · 2017.07.24 10:57
为什么我执行npm start就出错,前面的步骤一直跟着你走

赞 回复
npm run start

2017.08.08 15:13 回复
@點的天空 npm run start 也跑步起来

2017.08.16 14:44 回复
先配置webpack.config.js就可以跑起来了。?

2017.08.20 13:12 回复
添加新评论 还有1条评论, 展开查看
1 2 下一页
被以下专题收入,发现更多相似内容
收入我的专题
转载的~react
react
React
react
技术
React
react
展开更多
推荐阅读 更多精彩内容
简书作者文风纤弱,首页资源配置不当
大面积情况,看标题就知道,无病呻吟,顾影自怜的很多。 情感和文风缺少力量,不关注现实,语言包括情感的程式化套路化是思想贫瘠的表现。 很多是鸡汤文,表浅励志,充斥成功学和赚钱术的饥渴与鄙俗,又不是大俗,还弄的很自恋小资。 那么简书价值在哪里?写作,而非社交。写作是高度个人化的,系统推荐则应该是有差别的等级门槛的。 写作的根本意义是自娱,与精神淬炼。所以不要以阅读量为导向,而应该是鉴赏力为导向,用户也是需要被教育和塑造的。 微博上红人有巨大的点击量阅读量,有意义吗?当然商业上是有意义的,但写作并不必然趋向商业,或者说,刻意商业化并不是写作者可取方向和方法,作者的本分是创作有价值的内容。简书写...

咸叔
词/采桑子.离恨
曾经多少相思梦,遗落风中。 遗落风中。泪眼朦胧 、 离聚太匆匆。 红尘滚滚烟云弄,几许重逢。 几许重逢。 昨日今朝、恨比旧情浓。

雾夜忧魂
那一年的说说
在没有下载微信之前,我用了将近十年的QQ。开始使用微信之后,慢慢地就把QQ晾在一边了。 虽然几乎不用了,但也舍不得删除,每换一次手机,依旧会把QQ和QQ空间下载好,看着那两个图标安静地躺在手机桌面上,就像一直陪伴我的老朋友一样,心里很温暖。 那些年的QQ说说,类似于现在的微信朋友圈,点点滴滴,记录了生活的喜怒哀乐。 这是2012年的说说,摘录几篇孩子们的日常,重温一遍曾经的生活片段。 “单眼皮的人聪明,双眼皮的人善良”小丫指着正在看的一本书说“这书里说的哦,不信你看”,我说没错,因为我们家小丫就是个聪明的单眼皮。丫看看我的眼睛,安慰道“妈妈虽然是双眼皮,也很聪明的。” 我说“你永远要记住...

艺味深藏
别让孩子吃独食
1 烈日炎炎,酷暑难耐。 一个爸爸带着他五岁的女儿来买冰冻西瓜,孩子的奶奶也跟着。 他给女儿和老人各买了一块,自己却没有。 看穿着也是个外来普工,应该是暑假才把老人和孩子接过来团聚的。 他付过钱,把西瓜拿给女儿的时候,顺手掰下一小块,真的就一小块。 我以为他会把小块给孩子,吃完后再给她掰一块儿,毕竟孩子那么小,而两块钱一块的西瓜足有半斤重。 结果他自己一口吃掉了那一小块,把大块递给了孩子。 小姑娘接过西瓜,看到缺了一块儿,而她的爸爸正在用手背抿嘴巴。 她登时发作,大哭大叫,一边跺脚,一边嚷嚷:谁叫你吃的!谁叫你吃的! 小手一举,竟然把西瓜摔在了地上。 奶奶的西瓜还没有吃,连忙蹲下身来,怜...

艺味深藏

整个上午都没精神,我知道,我还对凌晨发生的事心有余悸,但更多的是对下午面对爸妈下班回家恐惧。 昨天下午四点半,我坐上去爸妈这的车,整整八个小时的车程,邻座的女孩一直在打电话,致使我全程都没睡。我去的地方是最后一站,到站时,车上只剩零星几个人,十二点四十分左右,除了我,所有乘客或被接或坐了拉人的三轮车走了。三轮车也都走了,大概是知道没有下一批人会来了吧。 我穿着扎眼的白衬衫站在路边,环顾这个街道,昏暗的路灯,偶尔会从远处驶来开着灯的轿车,大多时候都是空旷。我不停的把手机按亮又按灭。 爸爸还没来,明明妈妈打电话说爸爸一会儿便到。前一刻我还骄傲的对前来拉客的三轮车说有人来接,后一刻我便被这气氛...

1白水鉴心
240
分享一个 react + redux 完整的项目,同时写一下个人感悟
做React需要会什么? react的功能其实很单一,主要负责渲染的功能,现有的框架,比如angular是一个大而全的框架,用了angular几乎就不需要用其他工具辅助配合,但是react不一样,他只负责ui渲染,想要做好一个项目,往往需要其他库和工具的配合,比如用redu...

48 苍都
240
使用React,Redux,redux-sage构建图片库(翻译)
看到这篇文章build an image gallery using redux saga,觉得写的不错,长短也适中. 文后有注释版的github代码库,请使用comment分枝. Flickr API可能需要有fQ的基本能力.可以使用google的翻译作为参考,这篇文章...

48 smartphp
240
React + DVA + ANTD
本文主要介绍自己使用React + DVA + ANT 的开发过程,也是在开发中慢慢学习,前半部分大多数是理论知识,后半部分是实例。 React React是近期非常火热的一个前端开发框架,当然也有很多人认为它不是一个框架,因为它仅仅是作为MVC模式中的V层用来构建UI。...

48 spilledyear
240
React中的Redux
学习必备要点: 首先弄明白,Redux在使用React开发应用时,起到什么作用——状态集中管理 弄清楚Redux是如何实现状态管理的——store、action、reducer三个概念 在React中集成Redux:redux + react-redux(多了一个概念——s...

想睡你的人很多,想和你一辈子的人很少|关于颜值的21个困扰
高晓松曾经有过一个疑问:如果他有吴彦祖的颜,看到的世界会不会不一样? 废话。 我们不说颜即正义,但外貌的确会给人生带来可见或不可见的影响。因此,“生活真谛挖掘机”踢踢向大家征集了一个问题:“外貌给你的人生带来哪些好处或困扰?” 收到很多回复,精选了21条给大家。(顺便,关注...

48 傅踢踢

转载于:https://www.cnblogs.com/xiaocongcong888/p/9428043.html

从零开始搭建一个react项目相关推荐

  1. 从零开始搭建一个vue项目 -- vue-cli/cooking-cli(一)

    从零开始搭建一个vue项目 -- vue-cli/cooking-cli(一) 1.vue-cli搭建一个可靠成熟的项目 1.介绍 vue-cli 我是去年六月份接触的vue1.0,当时还是个菜逼,当 ...

  2. React-从0到1搭建一个React项目(一)

    文章目录 架构介绍 第三方组件库 项目准备 环境准备 使用create-react-app初始化项目 创建项目目录结构 安装第三方库 利用工作之余的时间学习了react,今年一回来,部门要在公众号上面 ...

  3. 如何搭建一个react项目?

    使用react很长时间,写个搭建项目的文档,用于新项目的快速启动. 本项目使用的技术栈: create-react-app react-router ant-design react-redux 一. ...

  4. 从零开始搭建一个springCloud项目

    前言:springCloud,相信大家已经听过很多次了,现在各种大大小小的公司都在使用的微服务框架,包括我正在上班公司的项目里面使用到的就是springCloud,此文仅对于刚入行不久的小白,大佬们可 ...

  5. React入门:从零搭建一个React项目

    一.初始化项目 新建文件夹,文件名firstreact 文件夹名称不要用react,node这类关键字,后面使用插件时会发生错误. init项目环境,项目信息可默认或自行修改 mkdir firstr ...

  6. Webpack 搭建基础 react 项目环境

    现在比较火的前端框架 react 和 vue,大部分时候我们都是通过脚手架进行 搭建的,对于脚手架来说有一个很重要的打包模块 webpack,使用起来确实挺方便的.对于使用脚手架来搭建项目也有弊端,如 ...

  7. github项目怎么运行_利用 GitHub 从零开始搭建一个博客

    "NightTeam",一个值得加星标的公众号. 趁着周末,搭建了一下 NightTeam 的官方博客和官方主页,耗时数个小时,两个站点终于完工了. 由于 NightTeam 的域 ...

  8. spring boot:从零开始搭建一个项目 - day 5 Mybatis plus代码自动生成器

    spring boot:从零开始搭建一个项目 - day 5 Mybatis plus代码自动生成器 一.Mybatis plus代码自动生成器 1.引入配置 2.创建Controller 3.执行m ...

  9. spring boot:从零开始搭建一个项目 - day 4 控制台输出日志美化 + swagger2

    spring boot:从零开始搭建一个项目 - day 4 控制台输出日志美化 + swagger2 一.logback.xml配置日志美化 二.集成swagger2 1.引入依赖 2.编写配置文件 ...

最新文章

  1. 可变分区存储管理实验报告总结_操作系统实验报告-可变分区存储管理方式的内存分配回收...
  2. hadoop之 Zookeeper 分布式应用程序协调服务
  3. 指定wsus服务器,步骤 2:在服务器上安装 WSUS
  4. 「LOJ 2289」「THUWC 2017」在美妙的数学王国中畅游——LCT泰勒展开
  5. C语言实现Dijkstra(迪杰斯特拉)算法(附完整源码)
  6. waf可以查看post请求吗_WAF是如何被绕过的?
  7. c语言将程序写为动态库,VS下生成C程序静态库(LIB)及动态库(DLL)的方法
  8. Java中throw和throws的区别
  9. jenkins中文插件_Jenkins学习笔记(一)
  10. html一个div调用的一个php页面,打开一个HTML文件,一个div/iframe中内PHP
  11. java中的装饰模式讲解,java 中设计模式(装饰设计模式)的实例详解
  12. 心理测试:DISC性格测试(完整版)
  13. Unity3D中UGUI的RectTransform对齐方式详解
  14. 台式电脑怎么添加计算机硬盘,台式机怎么加硬盘 台式机加硬盘教程介绍【图文详解】...
  15. angularjs学习2---数据绑定与controller
  16. PHP7.1 mcrypt_module_open() is deprecated
  17. 华硕冰刃4不显示,拆开检查2个故障,如不仔细分析上电又要烧板
  18. C语言学习笔记[第11天]
  19. 自己动手写油猴脚本 - 简单优化微信读书网页版阅读体验
  20. Xshell下载安装教程和使用教程(超详细)

热门文章

  1. 牛客多校第二场补题(继续罚坐)
  2. 一款漂亮的轻量级bootstrap中文后台管理系统模版
  3. 打造有竞争力的SaaS 营销策略,赢得客户和市场份额
  4. 动态规划法——多段图的最短路径
  5. 资深老师告诉你机加工铣刀选用的原则
  6. c# 读取硬件信息并进行加密绑定
  7. (一)信源函数——randerr、randint、randsrc、wgn
  8. 项目Beta冲刺(团队)——05.25(3/7)
  9. 日本政府警告用户慎用谷歌应用程序商店
  10. 飞雪桌面软件因系统未注册wmp.dll报错