深入浅出 React 和 Redux

这是今年读完的第三本书,是一本纯技术的书。看纯技术的书,速度明显要慢些,需要理解更多的专业术语和实践操作。后面一段时间尽可能将技术的书籍和个人成长类的书籍分开阅读。下面是摘录的这本书中,个人认为比较重要的知识点。

第1章 React 新的前端思维方式

1.全局安装create-react-app脚手架

npm install --global create-react-app

2.所谓组件,简单说,指的是能完成某个特定功能的独立的,可重复的代码。 3.做同一件事的代码应该有高耦合性。 4.React的理念,归结为一个公式 UI = render(data),开发者只需关注渲染,而不用关心如何实现增量渲染。 5.所谓纯函数,指的是没有任何副作用,输出完全依赖于输入的函数,两次函数调用如果输入相同,得到的结果也绝对相同。 6.React利用Virtual DOM,让每次渲染都只重新渲染最少的DOM元素。Virtual DOM是对DOM树的抽象,本质上是一个对象。

第2章 设计高质量的React组件

1.作为软件设计的通则,组件的划分要满足高类聚(Hign Cohesion)和低耦合(Low Coupling)的原则。高内聚指的是把逻辑紧密相关的内容放在一个组件中;低耦合指的是不同组件之间的依赖关系要尽量弱化,也就是每个组件要尽量独立。 2.差劲的程序员操心代码,优秀的程序员操心数据结构和他们之间的关系。(Linus Torvalds,Linux 创始人) 3.当prop的类型不是字符串类型时,在JSX中必须用花括号 {} 把 prop 值包住,所以 style 的值有两层括号,外层花括号代表是 JSX 的语法,内层花括号代表这是一个对象常量。 4.React 利用 prop 来定义组件的对外接口,用 state 来代表内部的状态,某个数据选择用 prop 还是用 state 表示,取决于这个数据是对外还是对内。

第3章 从 Flux 到 Redux

1.一个 store 也是一个对象,这个对象存储应用状态,同时还要接受 Dispatcher 派发的动作,根据动作来决定是否要更新应用状态。 2.reducer 函数不光接受 action 为参数,还接受 state 为参数。也就是说,Redux 的 reducer 只负责计算状态,却并不负责存储状态。

    function reducer(state,aciton) => {
        const {counterCaption} = action;
        switch (action.type) {
            case ActionTypes.INCREMENT:
                return {...state,[counterCaption]:state[counterCaption] + 1};
            case ActionTypes.DECREMENT;
                return {...state,[counterCaption]:state[counterCaption] - 1};
            default:
                return sate;
        }
    }

3.Redux三个基本原则:唯一数据源( Single Source of Truth ); 保持状态只读 ( State is read-only ); 数据改变只能通过纯函数来完成( Changes are made with pure functions )。 4.在计算机编程的世界里,完成任何一件事,可能都有一百种以上的方法,但无节制的灵活度反而让软件难以维护,增加限制是提高软件质量的法门。 5.在 Redux 框架下,一个 React 组件 基本上就是要完成以下两个功能:第一,和 Redux Store 打交道,读取 Store 的状态,用于初始化组件的状态,同时还要监听 Store 的状态变化; 当 Store 状态发生变化时,需要更新组件状态,从而驱动组件重新渲染;当需要更新 Store 状态时,就要派发 action 对象;第二,根据当前的 props 和 state , 渲染出用户界面; 6.每个 Redux 应用只能有一个 Redux Store,在整个 Redux 的生命周期中都应该保持 Store的唯一性。

第4章 模块化 React 和 Redux 应用

1.从架构出发,开始实施一个新应用的时候,需要提前考虑的三件事:代码文件的组织结构;确定模块的边界;Store 的状态树。 2.reducer 目录包含所有 Redux 的 reducer; actions 目录包含所有 action 构造函数;components 目录包含所有的傻瓜组件; containers 目录包含所有的容器组件。 3.在最理想的情况下,我们应该通过增加代码就能增加系统的功能,而不是通过对现有代码的修改来增加功能。( Rober C.Martin) 4.不同功能模块之间的依赖关系应该简洁而清晰,也就是所谓的保持模块之间的低耦合性;一个模块应该把自己的功能封装的很好,让外界不要太依赖与自己内部的结构,这样不会因为内部的变化而影响外部模块的功能,这就是所谓高内聚性。 5.JSX中可以使用任何形式的 JavaScript 表达式,只要 JavaScript 表达式出现在符号 {} 之间,但是也只能是 JavaScript “表达式” , for 或者 while 产生的是语句而不是表达式,所以不能出现 for 或者 while。 6.import 语句不能够存在于条件语句中,只能出现在模块语句的顶层位置。

第5章 React 组件性能优化

1.“过早的优化是万恶之源。” — 高德纳 2.“我们应该忘记忽略很小的性能优化,可以说97%的情况下,过早的优化是万恶之源,而我们应该关心对性能影响最关键的那另外3%的代码。” — 高德纳 3.在自己的工作中一定要有量化的性能指标。 4.对于 DOM 元素类型,React 会保留节点对应的 DOM 元素,只对树形结构根节点上的属性和内容做一下对比,然后只更新修改的部分。 5.利用 react-redux 提供过的 shouldComponentUpdate 实现来提高组件渲染功能的方法,一个要诀就是避免传递给其他组件的 prop 值是一个不同的对象,不然会造成无谓的重复渲染。

第6章 React 高级组件

1.一个高阶组建就是一个函数,这个函数接受一个组件作为输入,然后返回一个新的组件作为输出,而且,返回的新组件拥有了输入组件所不具有的功能。这里提到的组件指的并不是组件实例,而是一个组件类,也可以是一个无状态组件的函数。 2.定义高阶组件的意义何在呢?首先,重用代码;其次,修改现有 React 组件的行为。根据返回的新组件和传入组件参数的关系,高阶组件的实现方式可以分为两大类:代理方式的高阶组价和继承方式的高阶组件。 3.代理方式的高阶组件,可以应用在下列场景中:操纵 prop ;访问 ref ; 抽取状态;包装组件。继承方式的高阶组件可以应用于下列场景:操纵 prop ;操纵生命周期。其中代理方式更加容易实现和控制。

第7章 Redux 和服务器通信

1.一个趋势是在 React 应用中使用浏览器原生支持的 fetch 函数来访问网络资源,fetch 函数返回的结果是一个 Promise 对象,Promise 模式能够让需要异步处理的代码简洁清晰。 2.通常我们在组件的 componentDidMount 函数中做请求服务器的事情,因为生命周期函数 componentDidMount 被调用的时候,表明装载过程已经完成,组件需要渲染的内容已经在DOM树上出现,对服务器的请求可能依赖于已经渲染的内容,在 componentDidMount 函数中发送对服务器请求是一个合适的时机。

componentDidMount() {
    const apiUrl = `/data/cityinfo/${cityCode}.html`;
    fetch(apiUrl).then((response) => {
        if(response.status !== 200) {
            throw new Error ('Fail to get response with status' + response.status);
        }
        response.json().then((responseJson) => {
            this.setState({weather:responseJson.weatherinfo});
        }).catch((error) => {
            this.setState({weather:null});
        });
    }).catch((error) =>{
        this.setState({weather:null});
    });
}

3.虽然被 fetch 广为接受,大有取代其他网络访问方式的架势,但是它有一个特性一直被人诟病,那就是 fetch 认为只要服务器返回一个合法的 HTTP 响应就算成功,就会调用 then 函数提供的回调函数,即使这个 HTTP 响应的状态码是表示出错了的 400 或者 500。正因为 fetch 的这个特点,所以我们在 then 中,要做的第一件事就是检查传入参数 response 的 status 字段,只有 status 是代表成功的 200 的时候才继续,否则以错误处理。 4.对任何输入输出操作只要记住一点:不要相信任何返回结果。 5.Redux 的单向数据流是同步操作,驱动 Redux 流程的是 action 对象,每一个 action 对象被派发到 store 上之后,同步地被分配给所有的 reducer 函数,每个 reducer 都是纯函数,纯函数不产生任何副作用,自然是完成数据操作之后立刻同步返回,reducer 返回的结果又被同步地拿去更新 store 上的状态数据,更新状态数据的操作立刻被同步给监听 store状态改变的函数,从而引发作为视图的 React 组件更新过程。 6.redux-thunk 的工作就是检查 action 对象是不是函数,如果不是函数就放行,完成普通 action 对象的生命周期,而如果发现 action 对象是函数,那么就执行这个函数,并把 store 的 dispatch 函数和 getState 函数作为参数传递到函数中区去,处理过程到此为止,不会让这个异步的 action 对象继续往前派发到 reducer 函数。 7.返回的结果和时间都是不可靠的,即使是访问同样一个服务器,也完全可能先发出的请求后收到结果。 8.切记,软件开发是团队活动,选用某种技术的时候,不光要看自己能不能接受,还要考虑团队中其他伙伴是否容易接受这种技术。毕竟,软件开发的终极目的是满足产品需求,不要在追逐看似更酷更炫的技术中心迷失了初心。

第8章 单元测试

1.单元测试是一种自动化测试,测试代码和被测的对象非常相关,比如测试 React 组件的代码就和测试 jQuery 插件的代码完全不是一回事。 2.在 create-react-app 创建的应用中自带了 Jest 库,在任何一个 create-react-app 产生的应用代码目录下,用命令执行 npm run test 就会进入单元测试的界面。 3.单元测试的要义是一次只测试系统的一个功能点。

第9章 扩展 Redux

1.所谓中间件是独立的函数,指的是中间件之间不应该有依赖关系,每个中间件应该能够独立被一个 Redux Store 使用,完成某一个特定功能。中间件的特点是:中间件是独立的函数;中间件可以组合使用;中间件有一个统一的接口。 2.函数式编程的一个重要思想就是让每个函数的功能尽量小,然后通过函数的嵌套组合来实现复杂功能。 3.开发一个 Redux 中间件,首先要明确这个中间件的目的,因为中间件可以组合使用,所以不要让一个中间件的内容太过臃肿,尽量让一个中间件只完成一个功能,通过中间件的组合来完成丰富的功能。 4.扩展 Redux 功能的两种方法:中间件和 Store Enhancer。

第10章 动画

1.React 提供的动画辅助工具 ReactCssTransitionGroup。 2.在网页中实现动画的两种方式:(1) CSS3 方式,也就是利用浏览器对 CSS3 的原生支持实现动画;(2) 脚本方式,通过间隔一段时间用 JavaScript 来修改页面元素来实现动画; 3.CSS3 Transition 对一个动画规则的定义是基于时间和速度曲线 (Speed Curve)的规则。换句话来说,就是 CSS3 的动画过程要描述成 “在什么时间范围内,以什么样的运动节奏完成动画”。 4.脚本方式不是浏览器原生支持,消耗的计算资源过多。 5.setInterval 和 setTimeout 并不能保证在指定的时间间隔或者延迟的情况下准时调用指定函数。 6.React 官方的 ReactCssTransitionGroup,能够帮助定制组件在装载过程和卸载过程中的动画,对于更新过程的动画,则不在 ReactCssTransitionGroup 考虑之列,可以直接用 CSS3 来实现。 7.React-Motion 库提供了更强大灵活的动画实现功能,利用“以函数为组件”的模式, React-Motion 只需要提供几个组件,这些组件通过定时向子组件提供动画参数,就可以让开发者自定义动画的工能。

第11章 多页面应用

1.React-Router 提供了一个名为 Link 的组件来支持路由链接,Link 的作用是产生 HTML 的链接元素,但是对这个链接元素的点击操作并不引起网页跳转,而是被 Link 截获操作,把目标路径发送给 Router 路由器,这样 Router 就知道可以让哪个 Route 下的组件显示了。

import React from 'react';
import Link from 'react-router';
const view = () => {
    return (
        <div>
            <ul>
                <li style={ {liStyle}}> <Link to="/home"> Home </Link> </li>
                <li style={ {liStyle}}> <Link to="/about"> About </Link> </li>
            </ul>
        </div>
    )
}

2.React-Router 提供了另外一个组件 IndexRoute,就是和传统上 index.html 是一个路径目录下默认页面一样, IndexRoute 代表一个 Route 下的默认路由。 3.使用 React-Redux 库的 Provider 组件,作为数据的提供者,Provider 必须居于接受数据的 React组件之上。 4.webpack 的工作方式是根据代码中的 import 语句和 require 函数来找到所有的文件模块。 5.当应用变得庞大时,需要考虑对应用的 JavaScript 代码进行分片管理,这样用户访问某个页面的时候需要下载的 JavaScript 大小就可以控制在一个可以接受的范围内。

第12章 同构

1.理想情况下,一个 React 组件或者说功能组件既能够在浏览器端渲染也可以在服务器端渲染生成 HTML,这种方式叫做“同构”(Isomorphic),也就是同一份代码可以在不同环境下运行。 2.TTFP (Time To First Paint) :指的是从网页 HTTP 请求发出,到用户可以看到第一个有意义的内容渲染出来的时间差; 3.TTI ( Time To Interactive) : 指的是从网页HTTP 请求发出,到用户可以对网页内容进行交互的时间。 4.TTFP 这个时间当然越短越好,也就是说应用要尽早显示有意义的内容给用户,不要让用户只对着一个空白屏幕发呆;TTI 肯定要比 TTFP 要长一些,因为没有内容哪里会有交互,当渲染出内容后,还要 JavaScript 代码给 DOM 元素添加事件处理函数才会有交互效果,这个过程需要一些时间。 5.React 组件描述了 render 过程,往里面塞进去 state 就能够得到用户界面 UI,这个过程既可以在浏览器端进行,也可以在服务器端进行,一份代码足矣,不像其他服务器端渲染,需要分别开发服务器端渲染的代码和浏览器端代码。 6.webpack-hot-middleware 的工作原理是让网页建立一个 websocket 链接到服务器,服务器支持 websocket 的路径由 path 参数指定。

const app = express();
    app.use(express.static(path.resolve(__dirname,'../build')));
    app.use(webpackDevMiddleware);
    app.use(require('webpack-hot-middleware')(compiler,{
        log:'console.log',
        path:'/_webpack_hmr',
        heartbeat:10*1000
}))

7.为了实现同构,需要实现以下功能:在服务器端根据 React 组件产生 HTML ;数据脱水和注水;服务器端管理 Redux Store; 支持服务器和浏览器获取共同数据源。