React---基础1(元素、JSX、组件(生命周期)、props、state、事件、条件渲染、ES5函数互换ES6类)

react 安装

通过 npm 使用 React

国内使用 npm 速度很慢,你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:

npm install -g cnpm --registry=https://registry.npm.taobao.org

npm config set registry https://registry.npm.taobao.org

这样就可以使用 cnpm 命令来安装模块了:

cnpm install [name]

使用 create-react-app(脚手架库) 快速构建 React 开发环境

create-react-app 是来自于 Facebook,通过该命令我们无需配置就能快速构建 React 开发环境。

create-react-app 自动创建的项目是基于 React + Webpack + ES6 + eslint 。

执行以下命令创建项目:

第一步,全局安装: cnpm install -g create-react-app
第二步,切换到想创建项目的目录: create-react-app my-app
第三步,进入项目文件夹: cd my-app
第四步,启动项目: npm start

npm start  开启开发服务,运行我们开发时环境

npm run build  项目开发完打包生成静态文件交给后端进项上线

npm test  测试(一般很少用)

npm run eject  默认webpack配置是隐藏的,执行此命令后会显示出来不会再隐藏了

VS Code 编辑器安装插件:

创建一个 js/jsx 文件:

rcc  创建一个 类 组件
rfc  创建一个 函数 组件

查看react版本及升级

当前的react版本在你的项目中的package.json文件查看

查看react最新版本

npm info react

安装react指定版

// @后面输入版本号
npm install --save react@17.0.1

create-react-app升级

第一步需要先查看create-react-app最新版本

npm info create-react-app

 接着查看你当前的create-react-app版本,如果你的版本是小于最新版本的话,那就需要继续往下看。

create-react-app -V 
or 
create-react-app --version

第二步:如果你的版本是小于上面这个最新版本的话。那么需要先卸载掉原先的旧版本: npm uninstall -g create-react-app

1、在卸载过程中如果遇到一个错误,这个错误返回并提示create-react-app,create-react-app.cmd这两个文件的路径时,那么就要在电脑上找到对应的文件并删除,我的提示的路径是在这

 删除了之后再次运行查看是否删除成功:create-react-app -V 或 create-react-app --version

2、接着继续安装npm install -g create-react-app,如果在安装过程中报如下错误时,那么就要找到create-react-app的文件夹位置并删除

这两步操作之后,继续全局安装npm install -g create-react-app,并查看版本号 

一、什么是React

React 是一个用于构建用户界面的 JAVASCRIPT 库。

React 主要用于构建UI,很多人认为 React 是 MVC 中的 V(视图)。

React 起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。

React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。

React 特点:

  • 1.声明式设计 −React采用声明范式,可以轻松描述应用。

  • 2.高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。

  • 3.灵活 −React可以与已知的库或框架很好地配合。

  • 4.JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。

  • 5.组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。

  • 6.单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。

React 面向组件编程-----初始化显示

问题1:数据应该保存在哪个组件内?
回答:若是某个组件需要,则写在该组件内;若是多个组件需要,则写在这些组件共同的父组件内。

问题2:如何在子组件中修改父组件的数据状态?(第二篇文章的 state状态提升)
回答:子组件中不能直接改变父组件中的state状态;状态在哪个组件,更新状态的行为就定义在哪个组件
解决方案:父组件中定义行为函数,并将函数传递给子组件,子组件通过调用该函数(传参)来达到修改状态。

一个最简单的React例子:

React 元素渲染

元素是构成 React 应用的最小单位,它用于描述屏幕上输出的内容。

const element = <h1>Hello, world!</h1>;

与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,React DOM 可以确保 浏览器 DOM 的数据内容与 React 元素保持一致。 

render():渲染虚拟DOM到真实DOM,render(virtualDOM, containerDOM),即render(虚拟DOM, 真实DOM)

// 1.创建虚拟DOM对象
const element = <h1>Hello, world!</h1>; // 不是字符串;JSX语法
// 2.将虚拟DOM渲染到真实DOM容器中
ReactDom.render(
    element,
    document.getElementById('root')
)

要将React元素渲染到根DOM节点中,我们通过把它们都传递给 ReactDOM.render() 的方法来将其渲染到页面上;

ReactDom.render()接受两个参数,第一个是要被插入的内容,第二个是插入到DOM或者说index.html的位置

一个与Html对比的简单组件:

class ShoppingList extends React.Componnet {
    render() {
        return (
            <div className="shopping-list">
                <h1>Shoping List for {this.props.name}</h1>
                <ul>
                    <li>Instagram</li>
                    <li>WhatApp</li>
                    <li>Oculus</li>
                </ul>
            </div>
        )
    }
}

// Example usage:  <ShoppingList name="muzidigbig" />

在这里,ShoppingList 是一个 React组件类,或 React组件类型。组件接受参数,称为属性 props, 并通过 render方法返回一个现实的视图层次结构。

render 方法返回您要渲染的内容描述,然后React接受该描述并将其渲染到屏幕上,特别是,render 返回一个React 元素,这是一个渲染内容的轻量级的描述。大多数
React 开发人员使用 JSX 语法,也是上述案例写到的语法。

JSX 语法的转换规则为: <div /> 语法在构建是被转换为 React.createElement('div')。因此,上面的例子等价于:

createElement():createElement(标签名, { 属性: 属性名 }, 内容)

return React.createElement('div', {className: 'shopping-list'},
    React.createElement('h1', /* h1 children ... */),
    React.createElement('ul', /* ul children ... */)
);

既然 JSX 在 React 开发者中这么流行,那 JSX 又是什么呢?
 

二、JSX 语法

JSX :看起来像HTML的 Javascript 一种拓展语法,能够让你的 Javascript 中和正常描述 HTML一样编写 HTML。

使用 JSX 语法可读性更强,书写更方便

JSX语法中,使用 {} 代替 ""

元素是构成 React 应用的最小单位,JSX 就是用来声明 React 当中的元素。 

/*
    jsx语法规则:
        1.定义虚拟DOM时,不能写引号
        2.标签中混入JS表达式时要用 {}
        3.样式的类名指定不要用 class,要用 className
        4.内联样式,要用 style={{ key:value,color: '#999', fontSize: '30px' }} 的形式去写
        5.只有一个根标签
        6.标签必须闭合
        7.标签首字母
            1)若小写字母开头,则将该标签为html中同名元素,若html中无该标签对应的同名元素,则报错
            2)若大写字母开头,react就去渲染对应的组件,若组件未定义,则报错。
        8.注释: {/* <input type="text" ref={c => this.input1 = c} /> */}

*/

在Google添加扩展程序:react developer tool

直接运行会很慢,所以在真实项目中一般先编译后运行。

我们不需要一定使用 JSX,但它有以下优点:

  • JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
  • 它是类型安全的,在编译过程中就能发现错误。
  • 使用 JSX 编写模板更加简单快速。

  你可以用 花括号 将任意 Javascript 表达式嵌入到 JSX 中。例如:表达式 1 + 2, 变量 user.firstName, 和函数 formatName(User) 等都可以嵌入使用

function formatName(user) {
    return user.firstName + ' ' + user.lastName;
}

const user = {
    firstName: 'harper',
    lastName: 'Perez'
}

const element = {
    <h1> Hello, {formatName(user)}! </h1>
}

ReactDOM.render (
    element,
    document.getElementById('root')
)

数据带页面标签的不能直接专一,具体转义写法如下:

<div dangerouslySetInnerHTML={{__html: 'cc &copy; 2015'}} />

自定义 HTML 属性

如果在 JSX 中使用的属性不存在于 HTML 的规范中,这个属性会被忽略。如果要使用自定义属性,可以用 data- 前缀。

请注意,为了方便阅读开发者们常将 JSX分割成多行包裹起来,因为这可以避免分号自动插入的陷阱,如

{ 1
2 } 3
// is transformed to
{ 1
;2 ;} 3;

用 JSX 指定属性值

  你可以用花括号嵌入一个 JavaScript 表达式作为属性值

// 用引号形式
const element = <div tableIndex="0"></div>;
// 用表达式,并且表达式用花括号包裹
const element = <img src={user.avatarUrl}></img>;

用 JSX 指定子元素

  如果是空标签,可以直接用 /> 闭合

const element = <img src={user.avatarUrl} />

   如果包含子标签(外面包裹一个<div>)

<div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
</div>

比起 HTMLJSX 更接近于Javascript,所以React DOM规范使用驼峰(camelCase)属性命名约定,而不是HTML属性名称,当然,html的部分属性名称也作为保留字,不可使用,例如 class
因此,class 在 JSX 中 变为 className, tableindex 变为 tableIndex

用 JSX 防止注入攻击

  在JSX 中嵌入用户输入是安全的:

const title = response.potentiallyMaliciousInput;
// 这样是安全的
const element  = <h1>{title}</h1>

  默认情况下, 在渲染之前, React DOM 会格式化(escapes) JSX中的所有值. 从而保证用户无法注入任何应用之外的代码. 在被渲染之前,所有的数据都被转义成为了字符串处理。 以避免 XSS(跨站脚本) 攻击。

元素渲染到DOM

  正常情况下,你的 index.html 文件下会有这么一个div

<div id='root'></div>

  这个root DOM 节点挂在所有React DOM的位置。正常情况下,对于一个React单页面应用构建,只需要一个单独的根DOM节点即可。但如果要把React整合到现有的APP中,则可能会使用到多个DOM节点。

  React利用render方法将React元素渲染到DOM上,一旦元素被渲染到页面了之后,就不能在修改器子元素或任何元素的属性,就像电影里的一帧,在某以特定的时间点的UI效果,那元素的更新呢?没错,就是重新 render

function tick() {
    cosnt element = {
        <div>
            <h1>Hello, world</h1>
            <h2>It is {new Date().toLocaleTimeString()}.</h2>
        </div>
    };
    ReactDom.render (
        element,
        document.getElementById('root')
    )
}

setInterval(tick, 1000);

我们可以在 JSX 中使用 JavaScript 表达式。表达式写在花括号 {} 中。

实际上,大多数 React 应用只会调用一次ReactDom.render(),而实现组件更新的办法就是将代码封装在有状态的组件中。

React 只更新必须更新的部分

  这正是 React 的强大之处。React DOM 会将元素及其子元素与之前版本逐一对比,并只对有必要更新的 DOM 进行更新, 以达到 DOM 所需的状态

  开发过程中,更应该每个时间点UI的表现, 而不是关注随着时间不断更新UI的状态, 可以减少很多奇怪的 bug

三、组件和属性

  组件 components 和属性 props,其中,属性是单词 property 的代码简写。

定义组件的两种办法

  定义组件有两种方式

  1. 函数式组件定义
  2. 类组件定义(常用)
      最简单的定义组件的方法就是写一个 Javascript 函数
function Welcome(props)  {
    return <h1>Hello, props.name</h1>
}

  这就是一个有效的组件,它接首了一个 props 参数,并返回了一个React元素,这是一个函数式组件,表面上看,他就是一个 Javascript函数。

类组件的定义则依赖ES6 的 class 来定义,下面这种定义方法和上方是等效的:

important React from 'react';
class Welcome extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}</h1>;
    }
}

渲染一个组件

// DOM标签作为组件
const element = <div />;
// React 元素作为组件
const element = <Welcome name="muzidigbig" />;

  当React 遇到一个代表用户定义组件的元素时,它将 JSX 属性以一个单独对象即
props对象 的形式传递给相应的组件
,例如

function Welcome(props) {
    return <h1>Hello, {props.mname] </h1>;
}
const element = <Wlecome name="muzidigbig" />;
ReactDOM.render(
    element,
    document.getElementById('root')
)

理解

  1. 调用 ReactDOM.render() 方法并向其传入了<Welcome name="muzidigbig" />组件
  2. Raect 调用 Welcome 组件,发现组件是函数定义的,随后调用该函数,并向其传入了 {name: 'muzidigbig'} 作为 props对象
  3. Welcome 组件返回 <h1>Hello, muzidigbig</h1>(虚拟DOM)
  4. React DOM 迅速更新为真实 DOM,使其页面显示为 <h1>Hello, muzidigbig</h1>

注意: 组件名称必须以大写字母开头。

原生 HTML 元素名以小写字母开头,而自定义的 React 类名以大写字母开头,比如 HelloMessage 不能写成 helloMessage。除此之外还需要注意组件类只能包含一个顶层标签,否则也会报错。

React 会将以小写字母开头的组件视为原生 DOM 标签。例如,<div /> 代表 HTML 的 div 标签,而 <Welcome /> 则代表一个组件,并且需在作用域内使用 Welcome

构成组件

  既然组件是单独的一个React元素,那他能单独工作,因此我们能在一个React 元素中多次引用到相同的组件, 举个例子:

function Welcome(props) {
    return <h1>Hello, {props.name}</h1>
}
function App() {
    return (
        <Welcome name="Sara" />
        <Welcome name="Lucy" />
        <Welcome name="Edite" />
    )
}

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

  通常情况下, React apps 都有一个单独的顶层的 App 组件。如果是在已有的应用中整合React,也需要由下至上的从小的组件开始逐步整合到视图顶层的组件中。

组件必须返回一个单独的根元素,这就是为什么我们要添加一个 <div>来包裹所有的<Welcome /> 元素的原因

提取组件

  对于一个React 元素,如果其中含有可复用或可能会重复使用的内容,不要害怕把它单拿出来多个更小的组件。

  提取组件可能看起来是一个繁琐的工作,但是在大型的 App 中可以回报给我们的是大量的可复用组件。一个好的经验准则是如果你 UI 的一部分需要用多次 (ButtonPanelAvatar),或者本身足够复杂(AppFeedStoryComment),最好的做法是使其成为可复用组件。

Props 是只读的

  无论你用函数或类的方法来声明组件,

  虽然 React 很灵活,但是它有一条严格的规则:**所有 React 组件都必须是纯函数,并禁止修改其自身 props **。所谓的纯函数就是:传入函数参数不会在函数执行过程中发生改变,比如自增操作 a++

  如果props是只读的,那传递给子元素(子组件)的参数岂不是不能修改了?那子元素如何与父元素做交互呢?React还给我们提供了状态属性 state供我们在子组件内部修改值

四、状态和生命周期

  状态state, 生命周期 liftcircle.
  之前说过,一旦元素被渲染了之后就不可改变了,但我们可以通过重新渲染的方法使页面得以刷新,同样我们提到过最常用的方法是编写一个可复用的具有状态的组件,这里的状态,就是我们将要说的 state

state

  我们对上述提过的计时器tick 中的计时功能封装成一个函数式组件如下:

function Clock(props) {
    return (
        <div>
            <h1>Hello, world!</h1>
            <h2>It is {props.date.toLocaleTimeString()}.</h2>
        </div>
    )
}

  在这个例子中,我们将计时功能代码封装成了一个独立的可复用的组件,并通过属性date的方式将参数传入,但还不能到达我们想要的结果,那就是不能再组件内部修改参数值,组件中显示的数据依旧受控于父组件中date属性传递过来的值,那如果我们把这个date属性也添加到Clock内部呢?来看下

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
)

  这时父组件中只保留了对计时组件Clock的一个单纯的引用。剩下的事情全部依托以组件Clock自己去实现。要怎么实现这个需求?这里React提出了另一个数据对象,即state它用来保存组件内部的数据,与props类似,不同的是state是组件私有的,并且由组件本身完全控制。它能实现数据在组件内部的修改和更新。怎么使用这个state?继续往下讲之前我们先拓展一个知识

  我们知道组件有两种定义方式,即函数式组件和类组件,虽然函数式组件更加简洁更加接近原生 javascript,但类组件却拥有一些额外的属性,这个类组件专有特性,就是状态生命周期钩子,到这里也能清楚知道状态的关键作用,然而函数式组件没有这两个特性,因此,在需要使用到状态state情况下,我们需要将函数式组件转换成类组件

函数式组件转化成类组件

  尝试把一个函数式组件转化成类组件,官网给出了以下步骤,以Clock组件为例

  1. 创建一个继承自 React.Component 类的 ES6 class 同名类
  2. 添加一个名为 render(){return  ...} 的空方法
  3. 把原函数中的所有内容移至 render()
  4. render() 方法中使用 this.props 替代 props
  5. 删除保留的空函数声明
class Clock extents React.Component {
    render() {
        return (
            <div>
                <h1>Hello, world</h1>
                <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
            </div>
        )
    }
}

  到此,Clock 组件已经成功被我们修改成了一个类组件,我们便可以在其中添加本地状态state生命周期钩子

// 创建组件
class Clock extends React.Component {
    // 用类构造函数constructor初始化 this.state
    constructor(props) {
        // 使用super()将props传递给基础构造函数,固定格式
        super(props);
        // 设定初始state
        this.state = {date: new Date()};
    }

    render() {
        return (
            <div>
                <h1>Hello, world</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        )
    }
}

state的简写方式 

        // let isHot = false;
        // 1.创建类式组件(首字母大写)
        class Weather extends React.Component {
            /* constructor(props) {
                super(props)
                // 初始化状态
                //  this.state = { isHot: false }
                // 解决 changeWeather 中this指向问题
                // this.changeWeather = this.changeWeather.bind(this)
            } */

            /* 
                类中可以直接写赋值语句,如下代码的含义是:给Weather的实例对象添加一个属性,名为a,值为4
                a = 4
            */
            // 初始化状态
            state = { isHot: false }

            // 自定义方法--调用赋值语句的形式+箭头函数
            changeWeather = () => {
                let { isHot } = this.state
                this.setState({ isHot: !isHot })
            }

            render() {
                console.log('render中的this:', this)
                let { isHot } = this.state
                return <h1 onClick={this.changeWeather}>今天天气{isHot ? '炎热' : '凉爽'}</h1>;
            }
        }

        // 2.渲染组件到页面
        ReactDOM.render(<Weather />, document.querySelector("#test"))
        /*
            ReactDOM.render(<MyComponent />.... 发生了什么?
                1.React解析组件标签,找到 MyComponent 组件。
                2.发现组件是使用类定义的,随后new出该类的实例,并通过该实例调用原型上的 render 方法。
                3.将 render 返回的虚拟DOM转为真实DOM,随后呈现在页面上。
        */

        function demo(params) {
            console.log('标题被点击了');
        }

  这样,我们的类组件Clock 就拥有了自己的属性 this.state.date,也就不需要引用组件向其传递值了,因此,我们可以把组件引用中的date属性删掉,最终,我们将其渲染到DOM上,只使用组件引用,其他都交给组件Clock自己去实现

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
)

  到这里就结束了?细心的你会发现,组件Clock只是实现了当前时间的显示,而我们要改装的功能是一个计时器,计时功能去哪里了?没实现啊?我们需要在组件Clock中找到一个合适的时机去实现这个功能,为此,React团队引入了 声明周期方法,也叫生命周期钩子

在类组件中添加生命周期方法

  在一个具有许多组件的应用程序中,在组件被销毁时释放所占用的资源是非常重要的。就像浏览器的垃圾回收机制,近期内不需要再用的资源,应该及时清除。

  当 Clock 第一次渲染到DOM时,我们要设置一个定时器 。 这在 React 中称为 “挂载(mounting)” 。它有一个生命钩子componentDidMount()

Clock 产生的 DOM 被销毁时,我们也想清除该计时器。 这在 React 中称为 “卸载(unmounting)” 。它的生命钩子是componentWillUnmount()

  我们的计时器是在页面加载之后,页面生成初始化状态,然后由计时器去触发状态的刷新,因此,在挂载完成是去设置计时器是个非常不错的选择

componentDidMount() {
    this.timerID = setInterval(
        () => this.tick(), 1000
    )
}

  挂载时我们声明了一个tick()方法,接下来我们就要实现这个方法,是用来触发UI更新。嗯哼?UI更新?我们的页面状态state不是已经更新了吗?为啥还要UI更新?

  这里有一个非常重要的方法:setState()。我们先把代码补充完整再说明

componentDidMount() {
    // ...
}

tick() {
    this.setState({
        date: new Date()
    })
}

componentWillUnmount() {
    // ...
}

setState()是React触发页面更新的第二个办法,第一个办法开篇即说过,即render()方法。setState作用就是通知React检查带状态的组件中是否含有差值。此时react会生成一个虚拟DOM与之前的版本进行对比,只有有必要更新时才会更新。关于 state 与 setState过程 在我的另一篇文章中有详细说明,有兴趣的可以翻过去看看。

  为什么不把tick()方法写到componentDidMount()中?因为tick()只是一个普通方法,他不需要在生命周期中触发,也不用自动触发。只要谁调用了触发即可。因此不需要也不能放在生命周期钩子函数中。

  现在这个时钟每秒都会走了。整理一下,我们整个计时器代码如下:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

整个流程的执行过程是这样的:

  1. <Clock /> 被传入ReactDOM.render() 时, React 会调用 Clock组件的构造函数。 因为 Clock 要显示的是当前时间,所以它将使用包含当前时间的对象来初始化 this.state。我们稍后会更新此状态。

  2. 然后 React 调用了 Clock 组件的 render() 方法。 React 从该方法返回内容中得到要显示在屏幕上的内容。然后,React 然后更新 DOM 以匹配 Clock 的渲染输出。

  3. Clock 输出被插入到 DOM 中完成时,React 调用 componentDidMount() 生命周期钩子。在该方法中,Clock 组件请求浏览器设置一个定时器来一次调用 tick()

  4. 浏览器会每隔一秒调用一次 tick()方法。在该方法中, Clock 组件通过 setState() 方法并传递一个包含当前时间的对象来安排一个 UI 的更新。通过 setState(), React 得知了组件 state(状态)的变化, 随即再次调用 render() 方法,获取了当前应该显示的内容。 这次,render() 方法中的 this.state.date 的值已经发生了改变, 从而,其输出的内容也随之改变。React 于是据此对 DOM 进行更新。

  5. 如果通过其他操作将 Clock 组件从 DOM 中移除了, React 会调用 componentWillUnmount() 生命周期钩子, 所以计时器也会被停止。

正确的使用State(状态)

  对于setState() 有三件事情是我们应该要知道的

(1)不要直接修改state
  真正触发React对比不同版本的虚拟DOM是setState() 方法,直接修改state页面不会刷新,这一点与原生javascript区别较大,需要理解。

// 这么做不会触发React更新页面
this.state.comment = 'hello';
// 使用 setState() 代替
this.setState({ comment: 'hello' });

【注意】在组件中,唯一可以初始化分配this.state的地方就是构造函数constructor(){}

(2)state(状态)更新可能是异步的
  React为了优化性能,有可能会将多个setState() 调用合并为一次更新。这就导致 this.propsthis.state 可能是异步更新的,你不能依赖他们的值计算下一个state(状态)

// counter 计数更新会失败
this.setState({
    counter: this.state.counter this.props.increment
})

如果我们有这种需求,可以使用以下setState()办法:

// ES6 箭头函数法
this.setState((prevState, props) => ({
    counter: prevState.counter + props.increment
}));
// 常规函数法
this.setState(function(prevState, props) {
    return {
        counter: prevState.counter + props.increment
    };
})

(3)state(状态)更新会被合并
当你调用setState(), React会你提供的对象合并到当前state状态中。例如,你的状态可能包含几个独立的变量,然后你用几个独立的setState方法去调用更新,如下

constructor(props) {
    super(props);
    this.state = {
        posts: [],
        comments: []
    };
}

componentDidMount() {
    fetchPosts().then(response => {
        this.setState({
            posts: response.posts
        });
    });

    fetchComments().then(response => {
        this.setState({
            comments: response.comments
        });
    });
  }

  合并是浅合并,所以,this.setState({comments})在合并过程中不会改变this.state.posts的值,但是会完全替换this.state.comments 的值。

数据向下流动

  无论是作为父组件还是子组件,它都无法或者一个组件是否有状体,同时也不需要关心另一个组件是定义为函数组件还是类组件。这就是为什么state经常被称为 本地状态封装状态 的原因, 他不能被拥有并设置它的组件以外的任何组件访问。那如果需要访问怎么处理?
(1)作为其子组件的props(属性)

// 在组件中使用
<h2>It is {this.state.date.toLocaleTimeString()}</h2>
// 传递给子组件作为props
<FormattedDate date={this.state.date} />

  虽然FormattedDate组件通过props接收了date的值,但它仍然不能获知该值是来自于Clockstate, 还是 Clockprops, 或者一个手动创建的变量.

  这种数据关系,一般称为"从上到下"或"单向"的数据流。任何state(状态)始终由某个特定组件所有,并且从该state导出的任何数据 或 UI 只能影响树"下方"的组件

  如果把组件树想像为 props(属性) 的瀑布,所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。

各组件完全独立

  借用上文的Clock组件,我们创建一个App组件,并在其中渲染三个Clock:

function App() {
    return (
        // 之前说过组件只能返回一个根节点,所以用<div>包起来
        <div>
            <Clock />
            <Clock />
            <Clock />
        </div>
    );
}
ReactDOM.render(
    <App />,
    document.getElementById('root')
);

  每个Clock都设立它自己的计时器并独立更新,如果App中有一个数据变量,也能被三个Clock相互独立修改。

  至于何时使用有状态组件,何时使用无状态组件,被认为是组件的一个实现细节,取决于你当时的需求,你可以在有状态组件中使用无状态组件,也可以在无状态组件中使用有状态组件

属性扩散

有时候你需要给组件设置多个属性,你不想一个个写下这些属性,或者有时候你甚至不知道这些属性的名称,这时候 spread attributes 的功能就很有用了。

比如:

var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;

props 对象的属性会被设置成 Component 的属性。

属性也可以被覆盖:

var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // 'override'

 写在后面的属性值会覆盖前面的属性。

补充:State 与 Props 区别:

  • 组件通过属性(props) 和 状态(state)传递数据
  • props 是组件对外的接口,state 是组件对内的接口
    上层组件通过下层组件的props属性向下层组件传递数据或方法(state是从上到下单向流动的,从父级到子元素)
    换言之,
    下层组件通过 props 属性获取上层组件的数据或方法
  • props 是只读的
  • state 更新是异步的

五、事件处理

  通过 React 元素处理事件跟在 DOM 元素上处理事件非常相似。但是有一些语法上的区别:

  1. React 事件使用驼峰命名,而不是全部小写
  2. 通过 JSX , 传递一个函数作为事件处理程序,而不是一个字符串
// html usage
<button onclick="todo()">click me</button>
// React usage
<button onClick={todo}>click me></button>

   3.在React中不能通过返回false来阻止默认行为。必须明确的调用 preventDefault

// html usage
<a href="#" onclick="console.log('clicked'); return false">
    Click me
</a>
// React usage
class ActionLink extends React.Component {
    function handleClick(e) {
        e.preventDefault();
        console.log('clicked.');
    }
    return (
        <a href="#" onClick={handleClick}>
            Click me
        </a>
    )
}

  在这里,React团队帮Coder们实现了e事件的跨浏览器兼容问题。当使用React时,我们也不需要调用addEventListener在DOM 元素被创建后添加事件监听器。相反,只要当元素被初始渲染的时候提供一个监听器就可以了。

  当使用ES6类定义一个组件时,通常的一个事件处理程序就是类上的一个方法,看个例子,Toggle 组件渲染一个按钮,让用户在 “ON” 和 “OFF” 状态之间切换:

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 这个绑定是必要的,使`this`在回调中起作用
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

绑定类方法

  在JSX回调中你必须注意 this 的指向。 在 JavaScript 中,类方法默认没有 绑定 的。如果你忘记绑定 this.handleClick 并将其传递给onClick,那么在直接调用该函数时,this 会是 undefined

 这不是 React 特有的行为;这是 JavaScript 中的函数如何工作的一部分,可以使用属性初始值设置来正确地 绑定(bind) 回调,但这是实验性做法,不建议使用,以后有可能会废弃,如果你没有使用属性初始化语法
(1)可以在回调中使用一个 arrow functions

class LoginButton extends React.Component {
    handleClick() {
        console.log('this is: ', this)
    }

    render() {
        // 这个语法确保 `this` 被绑定在 handleClick 中
        return (
            <button onClick={(e) => this.handleClick(e)}>
                Click me
            </button>
        );
    }
}

(2)使用Function.prototype.bind 方法,相对简洁方便

<button onClick={this.handleClick.bind(this)}>
    Click me
</button>

传递参数给事件处理程序

  在循环内部,通常需要将一个额外的参数传递给事件处理程序,常用的有一下两种方案;

<button onClick={(e)  => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this,id)}>Delete Row</button>

  上面两个例子中,参数 e 作为 React 事件对象将会被作为第二个参数进行传递。通过箭头函数的方式,事件对象必须显式的进行传递,但是通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

六、条件渲染

在 React 中,你可以创建不同的组件封装你所需要的行为。然后,只渲染它们之中的一些,取决于你的应用的状态。

整个组件的条件渲染

  React 中的条件渲染就可在JS中的条件语句一样,使用JS操作符如if或者条件控制符来创建渲染当前的元素,并且让React更新匹配的UI。比如我们有一个需求,需要判断用户是否登录,来显示不同组件

function UserGreeting(props) {
    return <h1>Welcome back!</h1>
}

function GustGrreeting(props) {
    return <h1>Please sign up.</h1>
}
function Greeting(props) {
    const isLoggedIn = props.isLoggedIn;
    if (isLoggedIn) {
        return <UserGreeting />
    } 
    return <GuestGreeting />
}

ReactDOM.render(
    <Greeting isLoggedIn={false} />,
    document.getElementById('root')
);

使用元素变量条件渲染部分内容

  你可以用变量来存储元素。这可以帮助您有条件地渲染组件的一部分,而输出的其余部分不会更改。下方两个组件用于显示登出和登入按钮

function LoginButton() {
    return(
        <button onClick={props.onClick}>Login</button>
    )
}

function LogoutButton(props) {
    return (
        <button onClick={props.onclick}>Logout</button>
    )
}

  登入登出按钮已做好,接下来需要实现有切换功能的一个有状态的组件,为了更系统化学习,我们把前面的Greeting组件一起加进来

class LoginControl extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isLoginedIn: false
        }
    }

    handleLoginClick() {
        this.setState({   isLoggedIn: true });
    }

    handleLogoutClick() {
        this.setState({ isLoggedIn: false });
    }

    render() {
        const isLoggedIn = this.state.isLoggedIn;

        let button = null;
        if (isLoggedIn) {
            button = <LogoutButton onClick={this.handleLogoutClick.bind(this)} />
        } else {
            button = <LoginButton onclick={this.handleLoginClick.bind(this)} />
        }

        return (
            <div>
                <Greeting isLoggedIn={isLoggedIn} />{button}</div>
            </div>
        )
    }
}

reactDOM.render(
    <LoginControl />,
    document.getElementById('root')
)

使用if是很常见的一种做法,当然也有一些更简短的语。JSX中有几种内联条件的方法,

(1)使用逻辑与&&操作符的内联if用法
  我们可以在 JSX 中嵌入任何表达式,方法是将其包裹在花括号中,同样适用于JS的逻辑与&&运算符

function Mailbox(props) {
    const unreadMessages = props.unreadMessages;
    return (
        <div>
            <h1>Hello!</h1>
            { unreadMeaasges.length > 0 &&
                <h2> You have {unreadMessages.length} unread messages.
            }
        </div>
    )
}

cosnt message = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
    <Mailbox unreadMessages={messages} />,
    document.getElementById('root')
);

  该案例是可以正常运行的,因为在 JavaScript 中, true && expression 总是会评估为 expression ,而 false && expression 总是执行为 false 。并且我们可以在表达式中嵌入表达式

(2)使用条件操作符的内联If-Else(三目运算符)
  条件操作符 即三目表达式:condition ? trueExpression : falseExpression

// 条件渲染字符串
<div>The user is {isLoggedIn ? 'currently' : 'not'} logged in.</div>
// 条件渲染组件
<div>
    {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
    ) : (
        <LoginButton onClick={this.handleLoginClick} />
    )}
</div>

总之,遵循一个原则,哪种方式易于阅读,就选择哪种写法。并且,但条件变得越来越复杂时,可能是提取组件的好时机。

阻止组件渲染

  在极少数情况下,您可能希望组件隐藏自身,即使它是由另一个组件渲染的。为此,返回 null 而不是其渲染输出。注意这里是不渲染,不是不显示

  在下面的例子中,根据名为warnprops 值,呈现 <WarningBanner /> 。如果 props 值为 false ,则该组件不渲染:

function WarningBanner(props) {
    if (props.warn) { 
        return null;
    }
    
    return (
        <div className="warning">Warning</div>
    )
}

class Page extends React.Component {
    constructor(props) {
        super(props);
        this.state = { showWarning: true }
    }

    handleToggleClick() {
        this.setState(prevState => ({
            showWarning: !prevState.showWarning
        }));
    }

    render() {
        return (
            <div>
                <Warningbanner warn={this.state.showWarning} />
                <button onClick={this.handleToggleClick.bind(this)}>
                    { this.state.showWarning ?   'Hide' : 'Show'}
                 </button>
            </div>
        )
    }
}

ReactDOM.render(
    <Page />,
    document.getElementById('root')
)

从组件的 render 方法返回 null 不会影响组件生命周期方法的触发。 例如, componentWillUpdate componentDidUpdate 仍将被调用。因此需要组件刚载入时就要判断执行返回null


 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值