React-引领未来的用户界面开发框架-读书笔记(六)

前端之家收集整理的这篇文章主要介绍了React-引领未来的用户界面开发框架-读书笔记(六)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

第12章 服务端渲染

想让搜索引擎抓取到你的站点,服务端渲染这一步不可或缺,服务端渲染还可以提升站点性能,因为在加载JavaScript脚本的同时,浏览器就可以进行页面渲染。

React的虚拟DOM是其可被用于服务端渲染的关键。首先每个React Component 在虚拟DOM中完成渲染,然后React通过虚拟DOM来更新浏览器DOM中产生变化的那一部分,虚拟DOM作为内存中的DOM表现,为React在Node.js这类非浏览器环境下的运行提供了可能,React可以从虚拟DOM中生成一个字符串。而不是更新真正的DOM,这使得我们可以在客户端和服务端使用同一个React Component。

React 提供了两个可用于服务端渲染组件的函数:React.renderToString 和React.renderToStaticMarkup。

在设计用于服务端渲染的ReactComponent时需要有预见性,考虑以下方面。

  • 选取最优的渲染函数
  • 如何支持组件的异步状态。
  • 如何将应用的初始化状态传递到客户端。
  • 哪些生命周期函数可以用于服务端的渲染。
  • 如何为应用提供同构路由支持
  • 单例、实例以及上下文的用法

渲染函数

在服务端渲染React Component时,无法使用标准的React.render方法,因为服务端不存在DOM。React提供了两个渲染的函数,它们支持标准的React Component生命周期的一个子集。因而能够实现服务端渲染。

React.renderToString

React.renderToString是两个服务端渲染函数中的一个,也是开发主要使用的一个函数,和React.render不同,该函数去掉了用于表示渲染位置的参数。取而代之,该函数只返回一个字符串,这是一个快速的同步(阻塞式)函数,非常快。

var MyComponent = React.createClass({
    render:fucniton(){
        return <div> Hello World!</div>;
    }
});
var world= React.renderToString (<MyComponent/>);

//这个示例返回一个单行并且格式化的输出
<divdata-reactid=".fgvrzhg2yo"data-ract-checksum="-1663559667">
    Hello World!
</div>

你会注意到,React为这个<div>元素添加了两个data前缀的属性。在浏览器环境下,React使用data-reactid区分DOM节点。这也是每当组件的state及props发生变化时,React都可以精准的跟新制定DOM节点的原因。

data-react-checksum仅仅存在于服务端。顾名思义,它是已创建DOM和校验和。这准许React在客户端服用与服务端结构上相同点的DOM结构。该属性只会添加到根元素上。

React.renderToStaticMarkup

React.renderToStaticMarkup是第二个服务端渲染函数,除了不会包含React的data属性外,它和React.renderToString没有区别。

varMyComponent=React.createClass({
    render:function(){
        return<div>Hello World!</div>;
    }
});
varworld= React.renderToStaticMarkup(<MyCompoent/>);

//单行输出
<div>HelloWorld!</div>

用React.renderToString还是React.renderToStaticMarkup

每个渲染函数都有自己的用途,所以你必须明确自己的需求,再去决定使用哪个渲染函数。当且仅当你不打算在客户端渲染这个React Component时,才应该选择使用React.renderToStaticMarkup函数

下面有一些示例:

大多数情况下,我们都会选择使用React.renderToString。这将准许React使用data-react-checksum在客户端更迅速的初始化同一个React Component。因为React可以重用服务端提供的 DOM,所以它可以跳过生成DOM节点以及把他们挂载到文档中这两个昂贵的进程。对于复杂些的站点,这样做就会显著的减少加载时间,用户可以更快的与站点进行交互。

确保React Component能够在服务端和客户端准确的渲染出一致的结构是很重要的。如果data-react-checksum不匹配,React会舍弃服务端提供的DOM,然后生成新的DOM节点,并且将它们更新到文档中。此时,React也不再拥有服务端渲染带来的各种性能上的优势。

服务端组件生命周期

一旦渲染为字符串,组件就会只调用位于render之前的组件生命周期方法,需要指出,componentDidMount和componentWillUnmount不会在服务端渲染过程中被调用,而componentWillMount在两种渲染方式下均有效。

当新建一个组件时,你需要考虑到它可能即在服务端又在客户端进行渲染。这一点在创建事件监听器时尤为重要,因为并不存在一个生命周期方法通知我们React Component是否已经走完了整个生命周期。

在componentWillMount内注册的所有事件监听器及定时器都可能潜在的导致服务端内存泄漏。

最佳做法是只在componentDidMount内部创建事件监听器及定时器,然后在componentWilUnmount内清除这两者。

设计组件

服务端渲染时,请务必慎重考虑如何将组件的state传递到客户端,以充分利用服务端渲染的优势。在设计服务端渲染组件时,要时刻记得这一点。

在设计React Component时,需要保证同一个props传递到组件中,总会输出相同的初始渲染结果。坚持这样做将会提升组件的可测试性,并且可以保证组件在服务端和客户端渲染结果的一致性。充分利用服务端渲染的性能优势十分重要。

我们假设现在需要一个组件,它可以打印一个随机数。一个棘手问题是组件每次输出的结果总是不一致。如果组件在服务端而不是客户端进行渲染,checksum将会失效。

var MyComponent =React.createClass({
    render:dunction(){
        return <div>{Math.random()}</div>;
    }
});
var result = React.renderToStaticmarkup(<MyComponent/>);
var result2 = REact.renderToStaticMarkup(<MyComponent/>);

//result
<div>0.5820949131157249</div>

//result2
<div>0.420401582631672</div>

如果你打算重构它,组件将会通过props来接收一个随机数。然后,将props传递到客户端用于渲染。

var MyComponent= React.createClass({
    render :function(){
         retrun<div>{this.props.number}</div>
    }
});

var num=Math.random();
//服务端
React.renderToString(<MyComponentnumber={num}/);

//将num传递到客户端
React.render(<MyComponentnumber ={num}/>,document.body);

有多种方式可以将服务端的props传递到客户端。

最简单的方式之一是通过JavaScript对象将初始的props值传递到客户端。

<!DOCTYPEhtml>
<html>
    <head>
        <title>Example</title>
        <!--bundle 包括MyComponent、React等-->
        <script type =“text/javascript"src="bundle.js"></script>
    </head>

    <body>
        <!--服务端渲染MyComponent的结果-->
        <div data-reactid=".fgvrzhg2yo" data-react-checksum="-1663559667">
            0.5820949131157249
        </div>

        <!--注入初始props,供服务端使用-->
        <script type="text/javascript"> var initialProps = {"num":0.5820949131157249}; </script>

        <!--使用服务端初始props-->
        <script type-"text/javasript"> var num = initialProps.num; React.render(<MyComponent number={num}/>,document.body); </script>
    </body>

</html>

异步状态

很多应用需要从数据库或者网络服务这类远程数据源中读取数据,在客户端,这不是问题,在等待异步数据返回的时候,React Component可以展示一个加载图标。在服务端,React 无法直接复制该方案,因为render函数是同步的。为了使用异步数据,首先需要抓取数据,然后再渲染时将数据传递到组件中。

示例:

1、你可能需要从异步的store中转区用户记录;2、抓取到用户记录后,考虑到SEO以及性能等因素,需要在服务端渲染组件的状态;3、你需要让组件监听在客户端的变化,然后重新渲染

问题:React.renderToString是同步的,所以没有办法使用组件的任何一个生命周期方法,来抓区异步的数据

解决方案:使用statics函数来抓取异步数据,然后把数据传递到组件中用于渲染。将initialState作为props值传递到客户端。使用组件生命周期方法来监听变化,然后使用同一个statics函数更新状态。

var Username= React.createClass({
    statics:{
        getAsyncState:function()(props,setState){
            User.findById(props.userId)
                .then(function(user){
                    setState({user:user});
                })
                .catch(function(error){ setState({error:error}); });
        }
    },//客户端和服务器
    componentWillMount:function(){
        if(this.props.initialState){
            this.setState(this.props.initialState);
        }
    },136)">//仅客户端
    componentDidMount:function(){
        //如果props中没有,则获取一部state
        if(!this.props.initialState){
            this.updateAsynState();
        }
        //监听change事件
        User.on('change',this.updateAsyncState);
    },136)">//仅客户端
    componentWillUnmount:funtion(){
        //停止监听change事件
        User.off(‘change’,this.undateAsyncState);
    },updateAsyncState:function(){
        //访问示例中的静态函数
        this.constructor.getAsyncState(this.props,this.setstate);
    },render:funciton(){
        if(this.state.error){
            return <div>{this.state.error.message}</div>;
        }
        if(!this.state.usr){
            retrun<div>Loading...</div>
        }

        return <div>{this.state.user.username}</div>;
    }
});

//在服务器端渲染
var props={
    userId:123//也可以通过路由传递
};
Username.getAsyncState(props,funciton(initialState){
    props[initialState]=initialState;
    var result =React.renderToString(Username(props));
    //使用initialState将结果传递到客户端
});

上述解决方案中,预先抓取到异步数据这一步仅在服务端是必须的。在客户端,只有初次渲染时需要查找服务端所传递的initialState。后续客户端上的路由变化(比如HTML5,pushState或者fragment change)都会忽略掉服务端所有的initialState。同时,在抓取数据时最好加载文案信息。

同构路由

对于任意一个完整的应用来说,路由都至关重要。为了在服务端渲染出拥有路由的React应用,你必须确保路由系统支持无DOM渲染。

抓取异步数据是路由系统及其控制器的职责。我们假设一个深度嵌套的组件需要一些异步的数据。如果SEO需要这些数据,那么抓取数据的职责应该被提升至路由控制器,并且这些数据应该被传递到嵌套组件的最内层。如果不用考虑SEO,那么在客户端的componentDidMount方法内抓取数据是没问题的。这与传统的Ajax加载数据方式类似。

考虑一个React同构路由解决方案时,需确保它具有异步状态支持,或者可以轻易地更改以支持异步状态。理想情况下,你也会倾向于使用路由系统来控制,将initialState传递到客户端。

单例、实例及上下文

在浏览器端,你的应用如同包裹在独立的气泡中一样。每个实例之间的状态不会混在一起,因为每个实例通常存在于不同的计算机或者同一台计算机的不同沙箱之中。这使得我们可以在应用架构中轻松地使用单例模式。

当你开始迁移代码并在服务端运行时,你必须要小心,因为可能存在同一应用的多个实例在相同的作用域内同时运行的情况。有可能出现应用的两个实例都去更改单例状态的情况,这会导致异常的行为发生。

React渲染是同步的,所以你可以重置之前使用过的所有单例,而后在服务端渲染你的应用。如果异步状态需要使用单例,则又会遇到问题。同样,在渲染过程中使用抓取到的异步状态时,也需要考虑到这一点。

尽管可以在渲染前重置之前使用过的单例,但是在隔离的环境下运行你的应用总是有好处的。Contextify之类的包准许你在服务端彼此隔离地运行代码。这与客户端使用webworkers类似。Contextify通过将应用代码运行在一个隔离的Node.js V8实例中来工作。一旦加载完代码,你就可以调用环境中的所有函数。这种方法可以让你随意地使用单例模式,而不用考虑性能上的花销,因为每次请求都对应一个全新的Node.js V8实例。

React的核心开发小组不鼓励在组件树中传递上下文和实例。这种做法会降低组件的可移植性,并且应用内组件依赖的更改会对层级上的所有组件产生连动式的影响。这转而增加了应用的复杂性,而随着应用的增长,应用的可维护性也会降低。

当决定使用单例或者实例来控制你的上下文时,需要对两者权衡舍去。在选择一个方法之前,你需要估算出详细的需求,还需要考虑你所使用的第三方类库是如何架构的。

总结

服务端渲染是构建搜索引擎优化的Web站点和Web应用时的重要部分。React支持在服务端和客户端浏览器中渲染相同的React component。要有效地做到这一点,你需要保证整个应用都使用这一架构方式以支持服务端渲染。

第13章 周边类库

围绕着Ract,facebook还开发了一系列的前端工具。在你的React项目中,这些工具不是非用不可的,不过它们确实可以和React一起完美的工作。例如:

  • Jest
  • Immutable.js
  • Flux

Jest

Jest是Factbook开发的一个测试运行工具。它基于Jasmine测试框架提供相近的方式,使用大家熟悉的类似于expect(value).to(other)的断言。它提供了默认的模拟行为,会自动模拟require()返回的CommonJS模块。让现有的代码变成可测试的。它使用了模拟的DOM API ,同时通过小巧的Node.js命令行工具进行运行,缩短每次测试运行的时间。

page 108~112

Immutable.js

不可以变数据结构(Immutable Data Structures)中的数据是不允许修改的。相反,如果数据需要改变,它们会返回原始数据的一个经过修改的拷贝。React跟Flux可以很好的结合不可变数据结构,带来代码的简洁和性能的提升。

Immutable.js提供了多个数据结构,可以有原生的JavaScript数据结构构造而成,在需要的时候,也可以转会原生的JavaSctipt数据结构。

immutable.Map

Immutable.Map可作为常规JavaScript对象的替代者来使用:

var question = Immutable.Map({desctiption:'who is your favorite superhero?'});

//使用.get从Map中取值
question.get('desctiption');

//通过.set更新值时返回一个新的对象
//原始对象保持不变
question2 = question.set('desctiption','Who is your favorite comicbook hero?');

//使用.merge合并两个对象得到第三个对象
//同样原来的对象没有任何变化
var title = {title : 'Question #1'};
var question3 = question.merge(question2,title);
question3.toObject();//{title: 'Question #1',desctiption":'who is your favorite comicbook hero'}

Immutable.Vector

可以使用Immutable.Vector代替数组:

var options = Immutable.Vector('Superman',0)">'Batman');
var option2 = options.push('Spiderman');
option2.toArray(); //['Batman',0)">'Spiderman']

你还可以对这些数据结构进行嵌套:

var options = Immutable.Vector('Batman');
var question = Immutable.Map({
    description : 'who is your favorite superhero?',options : options
});

Immutable.js还有更丰富的特性,你可以到immutable-js获取更多的相关信息。

Flux

Flux是Facebook在发布React时发布的一种模式。它显著的特性是严格的单向数据流。

Facebook在GitHub发布了一份关于实习那Flux的参考,可以通过flux访问到。

Flux包含了三个重要的组件

  • Dispatcher
  • Store
  • View

下图清晰地展示了如何将这些部件组合到一起:

Flux没有强制的依赖,你可以任意选取自己需要的模块。

关于Flux更详细的讨论见第16章。

猜你在找的React相关文章