React & Redux 入门教程 - 第五篇:React & Redux 工具箱

本文是《React & Redux 入门教程》系列文章的第五篇,也是最后一篇。本篇我们将介绍 React & Redux 相关的一些工具。工欲善其事,必先利其器。首先我们会介绍 react-redux 这个项目,看看官方推荐我们怎么对 React 和 Redux 做 binding。然后我们会介绍 Babel,学习如何使用和扩展新的 JavaScript 语法。最后我们介绍 webpack,学习如何对程序进行打包和压缩。

react-redux

在上一篇教程中所写的 React & Redux 程序中,我们是这样对 React 和 Redux 做 binding 的:

// Bind React and Redux
const render = () => ReactDOM.render(
  <MarkdownEditor markdown={store.getState().markdown} updateMarkdown={
    (markdown) => store.dispatch({ type: "UPDATE_MARKDOWN", markdown })} />,
  document.getElementById('root')
);
render();
store.subscribe(render);
More...

React & Redux 入门教程 - 第四篇:引入 Redux

本文是《React & Redux 入门教程》系列文章的第四篇。在 React & Redux 入门教程 - 第一篇:React 术语浅析 中,我们学习了 React 的术语。在 React & Redux 入门教程 - 第二篇:从 jQuery 到 React 中,我们将一个传统的基于 jQuery 的项目迁移到了 React。在 React & Redux 入门教程 - 第三篇:Redux 术语浅析 中,我们学习了 Redux 的术语。

本篇是第二篇的后续:在一个 React 项目中引入 Redux。首先我们会回顾在第二篇中完成的 React 程序,然后我们会分析为什么以及什么时候需要用 Redux 来管理 state,最后我们会给出 Redux 版本的程序并分析其实现细节。

一个 React 程序

首先我们来回顾一下在第二篇的时候我们创建的 React 程序:

我们重点看一下 React 是如何管理 state 的。首先,在 component 的构造函数中,我们通过 this.state = { markdown: '' } 初始化了 state。其次,我们通过 {this.state.markdown} 来把 state 中的数据传递给 element。 最后,我们通过 onChange={(e) => this.setState({ markdown: e.target.value })} 来实现 state 的数据更新。

More...

React & Redux 入门教程 - 第三篇:Redux 术语浅析

本文是《React & Redux 入门教程》系列文章的第三篇。在 React & Redux 入门教程 - 第一篇:React 术语浅析 中,我们学习了 React 的术语。在 React & Redux 入门教程 - 第二篇:从 jQuery 到 React 中,我们将一个传统的基于 jQuery 的项目迁移到了 React。接下来我们要学习 Redux 了。我们还是从术语开始学起。

这一篇主要分析各种 Redux 术语,参考来自技术官网的资料,夹杂了我的个人体会和理解。通过阅读这篇文章,读者可以全面掌握各种核心 Redux 术语。在听别人讨论相关技术问题的时候,能够及时进入语境,理解别人谈话的内容,不再如闻天书、不知所云。

Redux

Redux 在 GitHub 上面有两万多颗星,是最流行的 Flux 实现。 很多人认为,一旦 React 程序变复杂,就要引入 Redux。GitHub 上面很多 React 的项目同时也使用了 Redux。

Redux is a predictable state container for JavaScript apps.

Redux 本质上是 JS 程序中 state 的容器。它通过加入一些限制,使得 state 的修改可预测,可以帮助你写出在不同环境下行为高度一致的程序。

值得一提的是,Redux 和 React 并没有非常直接的关系,React 可以没有 Redux,Redux 也可以和 React 之外的其它 View 框架搭配使用。

State

State 可以是任意类型。但一般情况下,是一个对象,或者是一个 key-value 数据结构。在设计 state 数据类型和数据结构的时候,要确保它能够被序列化为 json。

State 代表了整个 Redux 程序的状态,它被 store 管理,并且是全局唯一的。

注意这里讲的是 Redux 的 state,不要和 React 的 state 搞混了。关于二者的区别与联系,我们摘录来自 Redux 作者的一段话:

Use React for ephemeral state that doesn’t matter to the app globally and doesn’t mutate in complex ways. For example, a toggle in some UI element, a form input state. Use Redux for state that matters globally or is mutated in complex ways. For example, cached users, or a post draft.

简单讲:React 的 state 是局部的或临时的,不会有复杂的变化;Redux 的 state 是全局的,可能会有复杂的变化。

Sometimes you’ll want to move from Redux state to React state (when storing something in Redux gets awkward) or the other way around (when more components need to have access to some state that used to be local).

根据具体情况,可能需要把数据在 React state 和 Redux state 之前做移动。有些开发者认为,有了 Redux 来管理 state,React 的 component 不应该再有自己 state,很显然这种看法是比较偏激的,不符合上述来自 Redux 作者的建议。

Action

Action 是一个对象类型,它必须有一个叫做 type 的字段,一般是 string 类型,其它字段则没有限制。

它表达了想要更改 state 的 intention,亦即它仅仅代表了想更改 state 的一个意愿,并非动作。通俗点讲,它描述的是,而非。初学者总会觉得 action 是一个函数,其实并不是,因为函数表达的是。这个术语确实有一定的迷惑性,请大家务必仔细体会下。

Reducer

Reducer 是一个 function,它接受 state 和 action 作为参数,返回一个新的 state。

Reducer 是 Redux 最重要的概念。它就是经典的 MapReduce 那个 reduce 的概念。也可以和 JavaScript 内置的 reduce 函数进行类比一下:

const result = [1,2,3,4].reduce((sum, item) => sum + item);

拿上述代码和 Redux 中 的 reducer 进行类比,state 就相当于 sum,action 就相当于 item

因为 reducer 必须是纯函数式的(没有副作用),所以 API call 不能放到里面。此外 reducer 接受的 state 是 immutable 的,不能修改它。如果想对它进行修改,只能创建一个新的 state。

Store

Store 是一个对象。一个 Redux app 只能有一个 store。Store 中保存了整个程序的 state。Store 有下面四个属性:

  1. dispatch(action)
  2. getState() getter方法,返回 state 对象。
  3. subscribe(listener) 监听者模式。注册监听者,当 state 发生改变的时候 listener callback 被调用。
  4. replaceReducer(nextReducer) 基本上用不到。它可以实现 hot reloading and code splitting。
More...

React & Redux 入门教程 - 第二篇:从 jQuery 到 React

在上一篇中,我们学习了 React 的各种术语。其中 compoment 是 React 的核心概念,它是界面封装和重用的单元。如果把 component 类比为 function, 那么 props 就是它的参数,state 就是它的内部变量,它返回的结果是一个 element。此外我们推荐用 JSX 来提升 React 代码的可读性。如果您对前面提到几个概念感到陌生,请先阅读本教程的前一篇: React & Redux 入门教程 - 第一篇:React 术语浅析

在这一篇中,我们主要以实践为主。我们来动手把一个传统的基于 jQuery 的程序迁移到 React。首先我们会看一个 jQuery 程序的实现方式,接下来我们分析 jQuery 的优缺点,然后我们会指出为什么 React 在程序复杂的情况下比 jQuery 更合适,最后我们会动手用 React 将程序重新实现一遍,并比较两种不同实现的细节。

一个 jQuery 程序

这是一个简单的 markdown editor. 用户在上面的 textarea 输入 markdown 代码,程序会自动在下面显示出 HTML 版本的预览。程序的功能非常简单,我们重点看它的编码实现。

通过阅读代码,可以看出这是一个比较典型的 jQuery 程序。它通过 jQuery selector 来引用 DOM 元素:$('#markdown-textarea'), 通过 $('#markdown-textarea').on 来监听用户输入文字的事件,通过 $(this).val 来获取用户的输入,最后通过命令式的指令来修改 DOM 元素的值:$('.markdown-body').html(html);

More...

React & Redux 入门教程 - 第一篇:React 术语浅析

本文是《React & Redux 入门教程》系列文章的第一篇。《React & Redux 入门教程》目标读者为有一定前端开发基础的 React & Redux 的初学者。

这一篇文章主要分析各种 React 术语,参考来自技术官网的资料,夹杂了我的个人体会和理解。通过阅读这篇文章,读者可以全面掌握各种核心 React 术语。在听别人讨论相关技术问题的时候,能够及时进入语境,理解别人谈话的内容,不再如闻天书、不知所云。

React

React 是 Facebook 出品的用来开发用户界面的 JavaScript 库。 React 在 GitHub 上面有5万多颗星, 火热程度可见一斑。React 可能是当前最值得学习的前端技术。

值得一提的是,React 仅仅是一个界面库,它仅仅提供了 MVC 中的 V。对于复杂的应用,多半还需要搭配其它的库一起使用,比如说 Redux。

套用官方的说法,React 要解决的问题是:

building large applications with data that changes over time.

我们能得出结论,React 适用的场景有两个特征:一个是规模比较大;另一个是有较频繁数据更新。

React 也能用于简单的、没有太频繁的数据更新的应用。但是这种情况下体现不出它的优势来。比如我曾经把一个基于 jQuery 的非常简单的程序迁移到了 React,发现反而 jQuery 的版本更加简短、可读性更好。

JSX

严格来讲 JSX 并不属于 React。但是 React 是推荐使用 JSX 的,并且官方的例子中,默认也都使用了 JSX。

JSX lets you create JavaScript objects using HTML syntax.

没有 JSX 的情况下,我们只能通过调用 React.createElement 来构建界面。JSX 是对 JS 语法的扩展,通过它写出的界面代码跟 HTML 很像,极大地提高了可读性。下面例子中的 render() 方法,就使用了 JSX:

render() {
  return (
    <div>Seconds Elapsed: {this.state.secondsElapsed}</div>
  );
}

可以把 JSX 形象地理解为内嵌在 JavaScript 中的 HTML。JSX 很容易让我们想起模板语言(template language),它比模板语言强的地方在于它能支持全部的 JavaScript 语言特性。虽然 JSX 看起来像 HTML,它本质上是 JavaScript。

JSX 唯一的问题在于包含了 JSX 代码的 JavaSript 文件没办法直接执行,需要预先编译一下。编译它一般是使用 Babel 搭配 React preset

More...

React从后端获取数据并渲染输出

class ServiceList extends React.Component{
    constructor(props){
        super(props)
        this.state={
            //我们使用state里面的services来保存所有的service
            //刚开始的时候,内容为空 
            services:[],
            //这里的view决定了我们要显示哪些service
            view:"type_a"
        }
    }
 
    render(){
        //当react库运行到render方法的时候,就会遍历所有state中service的项目
        const serviceShows = this.state.services.map((service,index)=>{
            //如果这个service的type和当前view相符,就把他添加到待显示内容的array中去
            if(service.type === this.state.view){
                return <div className="one-service" key={index}>{service}</div>
                //看到有说用index做为key是anti-pattern,这个我也没有过多研究
                //更好的方法肯定是用一些明确能代表数据的值,比如service.id这样会更好
 
                //但是有时候通过相同的index来欺骗react让他相信两个element是同一个element,又会有一些妙用
                //比如假如这里每个service会显示一个根据需要长度不同的label,css上面做一个transition效果
                //然后services分两批显示,即数量除以2,每次显示一半
                //如果使用不同的key,比如service.id,那么你会看到element不同的时候它们只是简单的被替换了
                //而如果总是使用每一批的index,那么你会看到label有一个动态的长度变换效果
                //因为react被我们欺骗了,label没有被替换,只是长度变化,所以css动画效果就生效了
 
            }
        })
        return(
            <div>
                {//这里,我们把待显示内容显示出来}
                {serviceShows}
            </div>
        )
    }
 
    //可以看到,我们的类被构造的时候本身自带的state中services是个空数组,我们需要用内容填充他
    //查看react的文档的《组件生命周期》这一页(这一页很重要,一定要明白各个函数在什么情况下会被触发),发现它建议我们在每个组件显示完毕
    //之后使用componentDidMount函数来获取需要的数据,那就照做
    componentDidMount(){
        //组件先按照services为空渲染一遍,你可以理解为先把网页框架渲染出来
        //渲染完毕之后就调用我们这里这个函数用ajax方法去服务器取数据
        const xhr = new XMLHttpRequest()
        //服务器随你喜欢,你可以用php,也可以用node,只要实现了标准的GET方法即可
        //对于post,put,delete等方法同理
        //而很显然,假如你的数据没有必要从数据库中提取,或者长期不变,也不怕泄密
        //那你完全可以在此请求一个json文件
        xhr.open("GET", "http://your.server.com/api/services", true)
        //根据情况选择是否要随get请求发送用于身份认证的信息
        xhr.withCredentials = true
        xhr.send()
 
        xhr.onreadystatechange = () =>{
            if(xhr.readyState == XMLHttpRequest.DONE){
                if(xhr.status == 200){
                    //你当然可以用其他方法编码你的返回信息,但是对于js的世界来说,还有什么比json更方便呢?
                    let gotServices = JSON.parse(xhr.responseText)
                    //好了,我们获得了service列表,使用setState方法覆盖当前元素的services数据
                    this.setState({
                        services : gotServices
                    })
                }
            }else{
                alert("ajax失败了")
            }
        }
    }
 
    //那么我们已经渲染了页面,也从服务器获得了数据,还把数据放到了应该放的位置,
    //还要做什么才能让我们写的这个component把新数据显示出来呢?
    //不用担心,react控制着所有的setState方法,
    //当他发现你对于某个component设置了新的state之后,他就会告诉那个component去再次执行render方法,
    //然后你再去看render方法,这次他因为this.state.services的内容不一样了自然就会渲染出不一样的内容啦
    //怎么样才能明显的感受到这一前一后两次渲染的存在呢?
    //在你的服务器端把刚才ajax请求的接口做个5秒钟的延迟,
    //你就会明显看到获得返回结果的那一瞬间新内容被刷出来啦
}
More...

从零搭建React全家桶框架教程

网上react、webpack、redux全家桶的文档零零散散的实在不好找,无意中看到一篇写的比较好的,留档备份。如果按照作者写的一步一步来是没有任何问题的。当时我是没发现这篇文档,硬着头皮啃的官方手册,看的头晕眼花的,最后在BBS里看到有人发了有某网站的视频就看了看写了写才搞明白怎么回事。现在学习全家桶可比我那时候幸福很多了。

文档地址:https://github.com/brickspert/blog/issues/1

More...

React的小坑记录

在日常使用React的时候,我们总会遇到些坑,用一篇小Note做下记录,方便日后查找。
1、先看下面这段代码

        let names=['Perl','Urus'];
        var myCompents =React.createClass({
            render:function(){
                return
                    <div>
                    { 
                        names.map(function(name){
                            return <div key={name}>Hello,{name}!</div>
                        })
                    }
                    </div>
            }
        });
        ReactDOM.render(
            <myCompents />,
            document.getElementById('reactContainer')
        )
More...