初探 React 组件

React 的核心思想就是使用组件化的思想来开发出一个个独立的组件,然后将组件再拼装成一个完整的应用,从 MVC 分层思想上来考虑的话,React 专注在 UI 展现层上。

在 React 的骨架 JSX 一文中,有详细的讲解过使用 JSX 来开发组件的结构,也提到了使用 Inline Style 可以给组件定义样式,这样能确保组件的独立性。除了结构和样式,一个独立完整的组件还包含一些其他的部分。

从一个简单的 Dropdown 组件来开启我们的 React 组件化之路。

首先需要说明的是,Demo 的代码都是 ES6 的语法,之后的 React 文章中用到的 JavaScript 代码都将使用 ES6 的语法,如果你还不了解 ES6,建议先了解一下它的语法。另外考虑到 Demo 代码量的问题忽略了组件的样式部分,假设样式是通过外链或 Inline Style 的形式加载进来的。

// dropdown.js
    class Dropdown extends React.Component {
        constructor (props) {
            super(props);

            this.state = {
                visibile: false
            };
        }

        onClick = () => {
            this.setState({
                visible: !this.state.visible
            });
        }

        render () {
            const displayValue = this.state.visible ? 'block' : 'none';

            return (
                <div className="dropdown">
                    <button className="btn btn-default" onClick={this.onClick}>Browsers</button>
                    <ul className="dropdown-menu" style={{display: displayValue}}>
                        <li><a href="#">Firefox</a></li>
                        <li><a href="#">Chrome</a></li>
                        <li><a href="#">Safari</a></li>
                        <li><a href="#">Opera</a></li>
                        <li><a href="#">Internet Expoler</a></li>
                    </ul>
                </div>
            )
        }
    };

    export default Dropdown;

state 状态

React 的组件使用 状态机 的概念来描述组件本身的可变数据部分。Dropdown 组件最基本的用户交互逻辑可以抽象成两个状态 收起展开,进一步用代码来描述就是 visible: false(收起)visible: true(展开)

state-machine-1

要获取组件的状态都需要通过 this.state 来获取,在 constructor(构造函数) 中,可以通过 this.state 来设置组件的初始状态

要改变状态就必须通过 this.setState 方法来改变,不能通过直接修改 this.state.xxx = "xxx" 来进行修改,因为 this.state 的值是 immutable(不可变) 的。可以认为 state 有自己的 settergetter 方法,这样能更好的理解。

visible: false/true 的状态用于关联到菜单的展开和收起的两种样式,点击的时候通过切换两种状态,React 根据状态来自动更新 UI。

state-machine-2

如果按照传统的开发逻辑,我们要控制 Dropdown 菜单的收起和展开,需要先获取到菜单的 DOM 元素,然后在点击时,直接改变 DOM 元素的样式。

React 使用状态机来简化了繁琐的 DOM 操作,传统开发模式中繁琐的 DOM 操作都可以转变成状态的改变,底层的 DOM 操作由 React 来接管。如果你在使用 React 开发应用的时候想着应该怎么操作 DOM,这时候需要反思一下是不是使用 React 的方式有问题。

props 属性

如果要把 Dropdown 组件做成公用的,我们需要做进一步的改进和优化。Dropdown 的按钮内容以及菜单的内容都需要依赖父组件来传递。

父组件将数据传递给子组件,这个时候就需要通过 props 来传递了。

// app.js

    // 加载Dropdown组件
    import Dropdown from './dropdown';

    class App extends React.Component {
        constructor (props) {
            super(props);
        }

        static defaultProps = {
            dropdownBtn: 'Browsers',
            dropdownItems: [
                'Firefox',
                'Chrome',
                'Safari',
                'Opera',
                'Internet Expoler'
            ]
        }

        render () {
            <div>
                <h1>This is a Dropdown demo.</h1>
                <Dropdown
                    btnText={this.props.dropdownBtn}
                    items={this.props.dropdownItems} />
            </div>
        }
    };

App 组件中需要用到 Dropdown 组件,先使用 import 来加载该组件。将 Dropdown 需要用到的数据存储到 App 组件的默认的 props 中。

// 组件默认的props都可以存放到这里
    static defaultProps = {...}

然后使用属性声明的方式传递给 Dropdown 组件。

// this.props包含了App中所有的属性
    <Dropdown btnText={this.props.dropdownBtn} items={this.props.dropdownItems} />

Dropdown 在接收到来自父组件的数据,也是通过 this.props 来访问。这里需要注意父子组件都有 props,都能通过 this.props 来访问,父组件是组件内部默认定义的,而子组件的是来自父组件的传递。

在 Dropdown 中使用父组件传递过来的 props,将原来的 render 方法稍做改进。

render () {
        const displayValue = this.state.visible ? 'block' : 'none';

        return (
            <div className="dropdown">
                <button className="btn btn-default" onClick={this.onClick}>{this.props.btnText}</button>
                <ul className="dropdown-menu" style={{display: displayValue}}>
                    {
                        this.props.items.map((item) => (
                            <li key={`item-{item}`}>
                                <a href="#">{item}</a>
                            </li>
                        ))
                    }
                </ul>
            </div>
        )
    }

子组件在接受父组件的数据时,可以对数据类型进行校验,并且可以校验数据是否可选,如果传递的数据结果不匹配,React 会给出提示,这对于应用的健壮性来说是一个非常好的设计,建议组件在接收数据的时候最好都能进行数据校验,这样能避免很多因为数据格式问题引起的异常。

需要注意的是,在遍历数组生成结构的时候,都需要加上 key , 用于标示该元素的唯一性。

在 Dropdown 的组件中,这些数据都是必须传递的,可以指定校验为必需。

static propTypes = {
        // 指定按钮内容是必需的,并且类型为字符串
        btnText: React.PropTypes.isRequired.string,
        // 指定items的数据是必需的,并且类型为数组
        items: React.PropTypes.isRequired.array
    }

更多的数据类型说明,请参见属性校验的官方文档。

event 事件

React 有自己实现的事件系统,该事件系统是基于标准 W3C 的 DOM 事件,并在此基础上对事件进行了封装,抹平了不同浏览器之间兼容性差异。为 Virtual DOM 绑定事件,直接在元素上使用驼峰式的属性声明的方式即可。


    <button onClick={this.onClick}>{this.props.btnText}</button>

    ...

    // event对象也传递给了事件处理函数
    onClick = (event) => {
        // console.log(event.type) => click

        this.setState({
            visible: !this.state.visible
        });
    }

在事件处理函数中,其 this 并不指向组件类,可以通过在构造函数中使用 bind 来改变,也可以用最简单的 ES6 的箭头函数。

想查看的 React 的事件系统支持哪些类型的事件,或者 event 对象都包含了哪些可用的属性,可以查看关于事件的官方文档。

lifecycle 生命周期

React 的组件有几大生命周期,在这些生命周期内,组件本身提供了一些事件接口,这些事件和 Virtual DOM 的事件不一样,是组件本身会触发的事件。

生命周期按照阶段来划分的话,按照先后顺序分为 5 个阶段:

  • 1. 初始化(指定默认的 stateprops 来初始化组件);
  • 2. 渲染(render);
  • 3. 装载(mount,装在前和装载完毕都有相应的事件);
  • 4. 更新(stateprops的更新,都会触发相应的事件);
  • 5. 卸载(unmount组件销毁);

再回到上面的 App 组件中,如果 Dropdown 的数据是依赖于服务器异步数据的话,可以利用组件的生命周期事件来加载数据。

给 App 组件增加一个 componentWillMount 事件,该事件会在组件即将装载的时候触发。

// app.js
    import Dropdown from './dropdown';

    class App extends React.Component {
        constructor (props) {
            super(props);

            // 设置一个空的state对象
            // 当第一次render的时候,直接使用state不会报错
            this.state = {};
        }

        componentWillMount () {
            // 使用fetch来获取一个数据
            fetch('http://xxx.com/data')
                .then((response) => response.json())
                .then((response) => {
                    // 数据加载成功后通过setState改变状态
                    this.setState({
                        dropdownBtn: response.dropdownBtn,
                        dropdownItems: response.dropdownItems
                    });
                });
        }

        render () {
            // 第一次render的时候state并没有数据
            // 当数据加载成功调用了setState后才会有数据,这时组件会重新render
            <div>
                <h1>This is a Dropdown demo.</h1>
                <Dropdown
                    btnText={this.state.dropdownBtn}
                    items={this.state.dropdownItems} />
            </div>
        }
    }

App 组件传递给 Dropdown 将原来的静态数据改成了动态数据,所以要将 props 换成 state。Dropdown 组件也需要将原来的数据校验做一些调整。

最终的 Dropdown 完整的代码是下面这样的。

// dropdown.js
    class Dropdown extends React.Component {
        constructor (props) {
            super(props);

            this.state = {
                visibile: false
            };
        }

        static propTypes = {
            // 指定按钮内容是必需的,并且类型为字符串
            dropdownBtn: React.PropTypes.string,
            // 指定items的数据是必需的,并且类型为数组
            dropdownItems: React.PropTypes.array
        }

        onClick = (event) => {
            // console.log(event.type) => click

            this.setState({
                visible: !this.state.visible
            });
        }

        render () {
            const displayValue = this.state.visible ? 'block' : 'none';

            return (
                <div className="dropdown">
                    <button className="btn btn-default" onClick={this.onClick}>{this.props.btnText}</button>
                    <ul className="dropdown-menu" style={{display: displayValue}}>
                        {
                            this.props.items.map((item) => (
                                <li key={`item-{item}`}>
                                    <a href="#">{item}</a>
                                </li>
                            ))
                        }
                    </ul>
                </div>
            )
        }
    }

而要将组件放到页面中渲染,可以通过 React.render 来调用。

<div id="root"></div>

    ...

    React.render(
        <App />,
        document.getElementById('root')
    );

Chrome 81 正式发布 !消灭混合内容最后一步~

Chrome 81 于前天正式发布了,这个版本其实最初是计划在 3 月 17 号 发布的,但由于冠状病毒(COVID-19)爆发而导致推迟到了现在。Chrome 81 的延迟也扰乱了 Google 正常的六周发布时间表。因此 Google 此前也宣布,下一个版本将直接跳过 Chrome 82 ,直接发布 Chrome 83。 下面我就来带大家看看 Chrome 81 有哪些重要的更新。

发布于:4天以前  |  23次阅读  |  详细内容 »

当浏览器全面禁用三方 Cookie

苹果公司前不久对 Safari 浏览器进行一次重大更新,这次更新完全禁用了第三方 Cookie,这意味着,默认情况下,各大广告商或网站将无法对你的个人隐私进行追踪。而微软和 Mozilla 等也纷纷采取了措施禁用第三方 Cookie,但是由于这些浏览器市场份额较小,并没有给市场带来巨大的冲击。

发布于:4天以前  |  19次阅读  |  详细内容 »

H5 直播的疯狂点赞动画是如何实现的?

直播有一个很重要的互动:点赞。 为了烘托直播间的氛围,直播相对于普通视频或者文本内容,点赞通常有两个特殊需求: 点赞动作无限次,引导用户疯狂点赞 直播间的所有疯狂点赞,都需要在所有用户界面都动画展现出来(广播用户使用websocket消息)

发布于:4天以前  |  15次阅读  |  详细内容 »

探索 Serverless 中的前端开发模式

最近关于 Serverless 的讨论越来越多。看似与前端关系不大的 Serverless,其实早已和前端有了渊源,并且将对前端开发模式产生变革性的影响。本文主要就根据个人理解和总结,从前端开发模式的演进、基于 Serverless 的前端开发案例以及 Serverless 开发最佳实践等方面,与大家探讨 Serverless 中的前端开发模式。本人也有幸在 QCon2019 分享了这一主题。

发布于:4天以前  |  17次阅读  |  详细内容 »

前端需要了解的9种设计模式

设计模式是对软件设计开发过程中反复出现的某类问题的通用解决方案。设计模式更多的是指导思想和方法论,而不是现成的代码,当然每种设计模式都有每种语言中的具体实现方式。学习设计模式更多的是理解各种模式的内在思想和解决的问题,毕竟这是前人无数经验总结成的最佳实践,而代码实现则是对加深理解的辅助。

发布于:4天以前  |  21次阅读  |  详细内容 »

为什么你的网页需要 CSP?

内容安全策略(CSP)是一个 HTTP Header,CSP 通过告诉浏览器一系列规则,严格规定页面中哪些资源允许有哪些来源, 不在指定范围内的统统拒绝。

发布于:4天以前  |  16次阅读  |  详细内容 »

10 种跨域解决方案(附终极方案)

嗯。又来了,又说到跨域了,这是一个老生常谈的话题,以前我觉得这种基础文章没有什么好写的,会想着你去了解底层啊,不是很简单吗。但是最近在开发一个 「vscode 插件」 发现,当你刚入门一样东西的时候,你不会想这么多,因为你对他不熟悉,当你遇到不会的东西,你就是想先找到解决方案,然后通过这个解决方案再去深入理解。

发布于:4天以前  |  27次阅读  |  详细内容 »

移动 Web 最佳实践(干货长文,建议收藏)

笔者在公司用 web 技术开发移动端应用已经有一年多的时间了,开始主要以 vue 技术栈配合 native 为主,目前演进成 vue + react native 技术架构,vue 主要负责开发 OA 业务,比如报销、出差、crm 等等,react native 主要负责即时通信部分,是在 mattermost-mobile的基础上修改的(mattermost 是一个开源的即时通讯方案)。

发布于:4天以前  |  22次阅读  |  详细内容 »

不可错过的实用前端工具

给大家整理了 25 个前端相关的学习网站和一些靠谱的小工具,包括一些小游戏、教程、社区网站和博客,以及一些资源网站,希望可以帮助到大家!

发布于:4天以前  |  22次阅读  |  详细内容 »

理解 WebView

我们通常使用 Chrome, Firefox, Safari, Internet Explorer 和 Edge 等浏览器来浏览网页。你也许正在使用其中一种浏览器阅读本文!虽然浏览器对于访问互联网内容的任务来说非常流行,它们还有一些我们从未过多关注过的竞争对手。这些竞争对手以 WebView 的形式被我们所熟知。这片文章将讲解 WebView 的神秘之处以及为什么它这么棒。

发布于:4天以前  |  23次阅读  |  详细内容 »

Facebook 前端技术栈重构分享

当我们考虑如何构建一个新的网络应用—一个为现代浏览器设计的、具有用户对Facebook(我们已知的)所有期望的功能,我们现有的技术栈无法支持我们所需要的类似于桌面应用的感觉和性能。

发布于:4天以前  |  19次阅读  |  详细内容 »

最多阅读

为Electron程序添加运行时日志 11月以前  |  4370次阅读
初探 React 组件 1年以前  |  2104次阅读
wordpress标签页的制作 1年以前  |  1970次阅读
500行PHP代码搞定富文本安全过滤 1年以前  |  1896次阅读
js动态创建类和实例化 1年以前  |  1877次阅读
Node.js下通过配置host访问URL 1年以前  |  1867次阅读
使用 SRI 增强 localStorage 代码安全 1年以前  |  1836次阅读
22个HTML5的初级技巧 1年以前  |  1834次阅读
浅谈浏览器的原生拖拽事件 1年以前  |  1810次阅读
第三版主题上线 1年以前  |  1798次阅读
CSS清除浮动 1年以前  |  1791次阅读
2014年度总结 1年以前  |  1743次阅读
【译】V8 团队眼中的 ES6、ES7及未来 1年以前  |  1738次阅读
利用服务器返回header来传输数据 1年以前  |  1723次阅读
获取元素的计算的样式 1年以前  |  1718次阅读