《深入浅出React和Redux》一书的部分章节例子代码演练及相关知识点

个人学习心得项目地址

前言

针对《深入浅出React和Redux》一书中,某一个被挑选的例子,完成之后,会创建一个相应的分支。

  1. 《深入浅出React和Redux》原书例子代码,传送门
  2. 在原书代码的基础上,把相关依赖升级到当前最新版(截止 2018-11-06)。
    • react v16.6.0
    • redux v4.0.1
    • react-redux v5.1.0
  3. 第一章代码位于 chapter-01,第二章的代码位于 chapter-02,依次类推。
  4. 子目录名即为分支名,如第四章代码目录下的子目录:todo_controlled_component,会有一个对应分支也叫 todo_controlled_component

PropTypes 依赖变化

react 的类型检查 PropTypes 自 React v15.5 起已弃用,请使用 prop-types。
《深入浅出React和Redux》一书示例代码使用的 react 是 15.4.1 版本,需要调整 PropTypes 的引用:

// 书中的代码是
import { PropTypes } from 'react';
// 要改为:
import PropTypes from 'prop-types';

扩展阅读:使用 PropTypes 进行类型检查

第二章,分支 controlpanel

# 切换至该分支
git checkout controlpanel
git pull

知识点

  1. 组件。
  2. 组件的 state、props。
  3. 父组件通过 props 向子组件传递数据。

第二章,分支 controlpanel_with_summary

# 切换至该分支
git checkout controlpanel_with_summary
git pull

知识点

  1. 组件的 props,父组件向子组件传递数据,包括传递函数。
  2. 子组件通过调用父组件的函数,来达到向父组件传递数据的目的。

第三章,分支 react-redux

# 切换至该分支
git checkout react-redux
git pull

到项目根目录,添加 redux 和 react-redux 依赖。
以下操作会添加最新版的 redux(截止 2018-11-06,版本为:4.0.1) 和 react-redux(截止 2018-11-06,版本为:5.1.0)

cnpm i redux --save
cnpm i react-redux --save

如果不事先添加 redux 依赖而直接添加 react-redux 依赖,会有警告:

peerDependencies WARNING react-redux@* requires a peer of redux@^2.0.0 || ^3.0.0 || ^4.0.0-0 but none was installed

知识点

  1. UI 组件(presentational component)(傻瓜组件)
  2. 容器组件(container component)
  3. 应用 redux 的三大原则
  4. redux 库:const store = createStore(reducer, initValues)
  5. React-Redux 库
    • connect(mapStateToProps, mapDispatchToProps)(componentName)
    • mapStateToProps
    • mapDispatchToProps

相关知识点,已经总结到文档:redux 知识点、参考链接

第四章,分支 todo_controlled_component

# 切换至该分支
git checkout todo_controlled_component
git pull

知识点

代码文件组织结构,以及确定模块的边界。

参考《深入浅出React和Redux》P75-81。

  1. 推荐目录组织方式:按照功能组织。
  2. 把一个目录看做一个模块,那么我们要做的是明确这个模块对外的接口,而这个接口应该实现把内部封装起来。
  3. 目录下人 index.js 文件,就是我们的模块接口。
  4. 各个模块之间只能假设其他模块包含 index.js 文件,要引用模块只能导入 index.js,不能够直接去导人其他文件。
  5. 导人一个目录的时候,默认导人的就是这个目录下的 index.js 文件, index.js 文件中导出的内容,就是这个模块想要公开出来的接口。
  6. 推荐使用 export(命名式)的方式导出模块,而不是用 export default(默认)的方式,因为 export default 在导入时,会增加代码量。

状态树的设计

参考《深入浅出React和Redux》P81-83。

  1. 一个模块控制一个状态节点。
  2. 避免冗余数据。
  3. 树形结构扁平。
  4. 只能通过 reducer 纯函数修改 state,不能直接修改 state。
    • 所以,push 和 unshift 会改变原来那个数组,是不能直接作用于 state 的。
    • 利用扩展操作符。

combineReducers

  • 利用 combineReducers 可以把多个只针对局部状态的“小的”reducer 合并为一个操纵整个状态树的“大的“ reducer。

  • 更妙的是,没有两个”小的“ reducer 会发生冲突,因为无论怎么组合,状态树上一个子状态都只会被一个 reducer 处理,Redux 就是用这种方法隔绝了各个模块。

  • 很明显,无论我们有多少“小的” reducer,也无论如何组合,都不用在乎它们被因为调用的顺序,因为调用顺序和结果没有关系。

  • 随着应用变得复杂,需要对 reducer 函数进行拆分,拆分后的每一块独立负责管理 state 的一部分。

  • combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore。

  • 合并后的 reducer 可以调用各个子 reducer,并把它们的结果合并成一个 state 对象。state 对象的结构由传入的多个 reducer 的 key 决定。

  • 最终,state 对象的结构会是这样的:

    {
      todos: ...
      filter: ...
    }
    
  • 通过为传入对象的 reducer 命名不同来控制 state key 的命名。例如,你可以调用 combineReducers({ todos: todoReducer, filter: filterReducer }) 将 state 结构变为 { todos, counter }。

  • 个人认为,更好的做法是直接用 reducer 名作为 state 的 key,使用 ES6 的简写方法:combineReducers({ todos, filter })。这与 combineReducers({ todos: todoReducer, filter: filterReducer }) 产生的 state 结果是一样的。

关于 state key 的使用,实际开发过程中还需要注意些什么呢?看笔者总结的踩坑经验:<a href="./redux.md#stateKey">state 的 key</a>

bindActionCreators

把原来笨重的函数调用过程封装起来,使最终的业务代码更加优雅。

代码修改及完善

添加新的依赖

cnpm i --save react-addons-perf
...
peerDependencies WARNING react-addons-perf@* requires a peer of react-dom@^15.4.2 but react-dom@16.6.0 was installed

意思是,需要 react v15.4.2 支持,所以,将 Store.js 修改如下:

// 以下代码删除
import Perf from 'react-addons-perf'
win.Perf = Perf;
const middlewares = [];
if (process.env.NODE_ENV !== 'production') {
  middlewares.push(require('redux-immutable-state-invariant')());
}

const storeEnhancers = compose(
  applyMiddleware(...middlewares),
  (win && win.devToolsExtension) ? win.devToolsExtension() : (f) => f,
);

export default createStore(reducer, {}, storeEnhancers);
// 上一行代码改为
export default createStore(reducer, {}, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

createStore 第三个参数:window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),是为了支持 Redux DevTools 插件。

警告 Warning: A component is changing an uncontrolled input of type checkbox to be controlled...

完整的警告如下:

Warning: A component is changing an uncontrolled input of type checkbox to be controlled. 
Input elements should not switch from uncontrolled to controlled (or vice versa). 
Decide between using a controlled or uncontrolled input element for the lifetime of the component. 
More info: https://fb.me/react-controlled-components
    in input (at todoItem.js:13)
    in li (at todoItem.js:7)
    in TodoItem (at todoList.js:14)
    in ul (at todoList.js:11)
    in TodoList (created by Connect(TodoList))
    in Connect(TodoList) (at todos.js:11)
    in div (at todos.js:9)
    in Unknown (at TodoApp.js:8)
    in div (at TodoApp.js:7)
    in TodoApp (at src/index.js:10)
    in Provider (at src/index.js:9)

这是因为 todoItem.js:13 代码中的 checkbox 的 checked 属性没有用 state 来记录,所以会警告,但这并不影响该示例的正常运行。
关于页面控件是否受控,以及相关问题,请看官方文档:Controlled Components

解决

为了消除以上警告,同时,为了更方便理解 state 变化会引起页面的重新渲染,作如下修改:

  1. 将 checkbox 的 onClick 事件删除,这样,点击 checkbox 控件不会有任何反应(checkbox 设置了只读属性)。
  2. 将设置待办事项状态的点击事件放到 label 上,添加了 a 标签。

    不过,a 标签的 href 属性只是 # 会引发另外的警告,这个下面再解决。

  3. 同时将 checkbox 的 checked 属性添加上去,其值就是待办事项的数据:currentState.completed,这是一个 bool 变量。
  4. 将变量 checkedProp 定义行 const checkedProp = completed ? {checked: true} : {}; 删除。
    最后,关键代码如下:
    <input className="toggle" type="checkbox" checked={completed} readOnly/>
    <label className="text"><a href="#" onClick={onToggle}>{text}</a></label>

a 标签的 href 属性只是 # 会引发的警告

./src/todos/views/todoItem.js
  Line 13:  The href attribute requires a valid value to be accessible. 
  Provide a valid, navigable address as the href value. 
  If you cannot provide a valid href, but still need the element to resemble a link, 
  use a button and change it with appropriate styles. 
  Learn more: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md  
  jsx-a11y/anchor-is-valid

项目中用到的 Link 组件 ./src/filter/views/link.js 也有同样的警告,一起修改。

./src/filter/views/link.js
  Line 11:  The href attribute requires a valid value to be accessible.
  ...

解决

参照文章anchor-is-valid,作如下调整:

将 a 标签 <a href="#" onClick={onToggle}>{text}</a> 换成 button 控件,同时增加 button 相关的 style.css 文件放到 src 根目录下。

<button
    type="button"
    className="link-button"
    onClick={onToggle}>
    {text}
</button>

将 link.js 组件中的 a 标签也换成 button,这里就不贴代码了,直接看代码文件吧。

最后还有一个警告,在点击【添加】按钮的时候触发的,不过 chrome 浏览器才有该警告,QQ 浏览器没有,没再深入研究。

[Deprecation] Using unescaped '#' characters in a data URI body is deprecated and will be removed in M71, around December 2018. 
Please use '%23' instead. See https://www.chromestatus.com/features/5656049583390720 for more details.

关于作者

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,104评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,816评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,697评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,836评论 1 298
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,851评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,441评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,992评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,899评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,457评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,529评论 3 341
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,664评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,346评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,025评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,511评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,611评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,081评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,675评论 2 359

推荐阅读更多精彩内容