React相关《上》

React是一个流行的JavaScript库,用于为网页或应用程序构建可重用、组件驱动的用户界面。

React将HTML和JavaScript功能结合到自己的标记语言JSX中。在JS文件中编写。

React还可以方便地管理整个应用程序中的数据流。下面将分别介绍如何创建不同的React组件、以状态属性的形式管理数据、使用不同的生命周期方法,如componentDidMount等。

React是一个由Facebook创建和维护的开源视图库,是呈现现代web应用程序的用户界面(UI)的好工具。

React使用名为JSX的JavaScript语法扩展,它允许我们直接在JavaScript中编写HTML,允许我们在HTML中使用JavaScript的全部编程功能,有助于保持代码的可读性。大多数情况下,JSX与HTML类似,但也有一些关键的区别。例如,因为JSX是JavaScript的语法扩展,所以实际上可以直接在JSX中编写JavaScript。为此,只需在花括号中包含希望被视为JavaScript的代码:{“这被视为JavaScript代码”}。然而,由于JSX不是有效的JavaScript,因此必须将JSX代码编译为JavaScript,对于这一过程,流行的工具是Babel。

JSX是一种语法,它会被编译成有效的JavaScript。JSX是一种在JavaScript中编写可读HTML的方便工具。有了React,我们可以使用React的呈现API(称为ReactDOM)将这个JSX直接呈现到HTML(DOM)。ReactDOM提供了一种将React元素呈现到DOM的简单方法:ReactDOM.render(componentToRender, targetNode),其中第一个参数表示要呈现的React元素或组件,第二个参数表示想要在targetNode这个DOM节点呈现组件。ReactDOM.render()必须在JSX元素声明之后调用,就像在使用变量之前必须声明变量一样。如:

const JSX = (
  <div>
    <h1>Hello World</h1>
    <p>Lets render this to the DOM</p>
  </div>
);
ReactDOM.render(JSX,document.getElementById('challenge-node'));

ReactDOM.render(JSX, document.getElementById('root'))这个函数调用将JSX放入React自己的轻量级DOM呈现,然后,React使用自己DOM的快照来优化更新实际DOM的特定部分(即使用ReactDOM.render()方法将组件JSX呈现到页面,使用document.getElementById()选择要将其呈献到哪个DOM节点)。

使用JSX将h1元素分配给常量JSX:

const JSX = <h1>Hello JSX!</h1>;

JSX也可以表示更复杂的HTML。关于嵌套JSX,它必须返回单个元素(可以理解为只返回父元素)。

一个父元素将包装所有其他级别的嵌套元素。几个没有父元素包装的兄弟元素编写的JSX元素将无法传输编译。

 有效的JSX:

<div>
  <p>Paragraph One</p>
  <p>Paragraph Two</p>
  <p>Paragraph Three</p>
</div>

无效的JSX:

<p>Paragraph One</p>
<p>Paragraph Two</p>
<p>Paragraph Three</p>

 JSX的注释方法为:{ /* */ }

JSX与HTML的一些区别:

一、JSX使用className来定义HTML类。此时不能再使用单词class来定义HTML类,因为class是JavaScript中的保留字。

const JSX = (
  <div className="myDiv">
    <h1>Add a class to this div</h1>
  </div>
);

二、JSX中所有HTML属性命名和事件引用命名约定都采用“小驼峰原则”。例如,JSX中的单击事件是onClick,而不是onclick,同样,onChange而非onchange。

三、任何JSX元素都可以用自闭标记编写,并且每个元素都必须是闭合的。例如,水平线标记要写为<hr />,换行标记要写为<br/>,才能成为可以传输的有效JSX。而HTML可以写为<hr>或<hr />、<br>或<br/>。

四、JSX中<div>可以写成<div/>或<div></div>。前者表示无法在<div/>中包含任何内容,此语法在渲染/呈现React组件时非常有用。

 React的核心是组件。React中的所有内容都是一个组件,如何创建一个组件呢?

有两种方法可以创建React组件

第一种方法是使用JavaScript函数,此法将会创建无状态函数组件(程序的状态概念稍后介绍)。现在,暂时将无状态组件视为可以接收并呈现数据,但不能管理或跟踪数据更改的组件。要创建带有函数的组件,只需编写一个返回JSX或null的JavaScript函数。注意,React要求函数名以大写字母开头!

在JSX中指定HTML类的无状态函数组件示例(传输后,<div>将有一个名为customClass的CSS类):

const DemoComponent = function() {
  return (
    <div className='customClass' />
  );
};

因为JSX组件代表HTML,所以我们可以将几个组件放在一起创建更复杂的HTML页面。这是React提供的组件体系结构的关键优势之一。它允许我们从许多独立、孤立的组件组成UI。这使得构建和维护复杂的用户界面更加容易。

第二种方法是使用ES6的类语法。如下例,创建了一个ES6类Kitten,Kitten扩展了“React.Component”类。因此,Kitten类现在可以访问许多有用的React特性,例如本地状态和生命周期钩子(后面会有介绍)。 注意,Kitten类中定义了一个调用“super()”的构造函数。它使用“super()”调用父类“React.Component”的构造函数。构造函数是一种特殊方法,它是使用class关键字创建的对象初始化时使用的。最好用super调用组件的构造函数,并将props传递给两者,这样可以确保组件正确初始化,包含此代码是标准的写法。

class Kitten extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <h1>Hi</h1>
    );
  }
}

如何将多个React组件组合在一起呢?假设我们正在构建一个App,现在已经创建了三个组件:Navbar、Dashboard、Footer,为了将它们组合在一起,我们可以创建一个名为App的父组件,将这三个组件中的每一个呈现(渲染)为子组件。在React组件中,要将组件呈现为子组件,需要在JSX中包含组件名称,这些组件名称成了我们自定义编写的HTML标签。例如,在render方法中,可以编写:

return (
 <App>
  <Navbar />
  <Dashboard />
  <Footer />
 </App>
)

当React遇到引用另一个组件的自定义HTML标签(如本例中的</>中包装的组件名称)时,它会在标签的位置呈现该组件的标记结构。这显示了“App”组件和“Navbar、Dashboard、Footer”之间存在父子关系。再看一个例子:

const ChildComponent = () => {
  return (
    <div>
      <p>I am the child</p>
    </div>
  );
};

class ParentComponent extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <h1>I am the parent</h1>
        <ChildComponent/>
      </div>
    );
  }
};
{ /* 页面会以相应的h1、p标记的字体显示:
I am the parent
I am the child
 */ }

React还有很多不同的方法可以组合组件。组件组成是React的强大功能之一。使用React时,要考虑用户界面组件,例如上面的App示例。我们可以将用户界面分解为基本的构建块,这些块将成为组件。这有助于将负责UI的代码与负责处理应用程序逻辑的代码分开,大大简化复杂项目的开发和维护。

React还能呈现嵌套组件:

const TypesOfFruit = () => {
  return (
    <div>
      <h2>Fruits:</h2>
      <ul>
        <li>Apples</li>
        <li>Blueberries</li>
        <li>Strawberries</li>
        <li>Bananas</li>
      </ul>
    </div>
  );
};

const Fruits = () => {
  return (
    <div>
      lalala
      <TypesOfFruit/>
    </div>
  );
};

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

  render() {
    return (
      <div>
        <h1>Types of Food:</h1>
        { /* 使用React呈现(渲染)嵌套组件 */ }
        <Fruits/>  
      </div>
    );
  }
};
{/*显示如下:
Types of Food:
lalala
Fruits:
.Apples
.Blueberries
.Strawberries
.Bananas 
 */}
View Code

上例定义了两个函数组件,称为“TypesOfFruit”和“Fruits”。取“TypesOfFruit”组件,将其组合或嵌套在“Fruits”组件中。然后将“Fruits”组件嵌套到“TypesOfFood”组件中。结果应该是子组件嵌套在父组件中,而父组件嵌套在自己的父组件中! 

注意,在其他组件中呈现ES6类组件与呈现简单组件是一样的。我们可以在其他组件中呈现JSX元素、无状态函数组件和ES6类组件: 

class Fruits extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <h2>Fruits:</h2>
        { /* NonCitrus和Citrus构成部分假设存在此页面,我们直接拿来用 */ }
        <NonCitrus/>
        <Citrus/>
      </div>
    );
  }
};

class TypesOfFood extends React.Component {
  constructor(props) {
     super(props);
  }
  render() {
    return (
      <div>
        <h1>Types of Food:</h1>
        <Fruits/>
        <Vegetables />
      </div>
    );
  }
};

还记得前面使用ReactDOM API将JSX元素呈现给DOM吗?React组件的渲染过程和它非常相似。上面几个例子主要集中在组件和合成上,因此渲染没有提到。但是,如果不调用ReactDOM API,我们编写的任何React代码都不会呈现到DOM。这里我们用到的ReactDOM API语法更新了:

ReactDOM.render(componentToRender,targetNode)第一个参数是要渲染的React组件。第二个参数是要在其中呈现该组件的DOM节点。

React组件传递到“ReactDOM.render()”中的方式与JSX元素稍有不同。对于JSX元素,传入的是要呈现的元素的名称。但是,对于React组件,需要使用与渲染嵌套组件相同的语法,例如“ReactDOM.render(<ComponentToRender/>,targetNode)”,此语法对于ES6类组件和函数组件的渲染皆适用。看个例子:

class TypesOfFood extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <h1>Types of Food:</h1>
        <Fruits />
        <Vegetables />
      </div>
    );
  }
};
{/* 将“TypesOfFood”渲染到DOM(即让DOM把组件包含的标签、内容都显示出来)。假设DOM有一个带有“id='challenge-node'”的div可供使用 */}
ReactDOM.render(<TypesOfFood/>,document.getElementById("challenge-node"));

React组件是React应用程序的核心构建块,因此熟悉编写它们很重要。记住,典型的React组件是一个扩展React.component的ES6类,它有一个返回HTML(来自JSX)或null的render方法。这是React组件的基本形式。确保调用组件的构造函数,使用ReactDOM.Render()将组件渲染到DOM。下面编写一个组件MyComponent :

class MyComponent extends React.Component {
  constructor(props){
    super(props)
  }
  render(){
    return (
      <div>
        <h1>My First React Component!</h1>
      </div>
    )
  }  
}
ReactDOM.render(<MyComponent/>,document.getElementById("challenge-node"));

在React中,可以将属性传递给子组件。如何将信息作为“props”或属性从父组件传递到子组件呢?。假设有一个名为'App'的组件,它呈现一个名为Welcome(无状态函数组件)的子组件。可以通过编写以下内容来给Welcome传递user属性:

<App>
  <Welcome user='Mark' />
</App>

可以使用自己创建并由React支持的自定义HTML属性来传递给组件。上面,创建的属性user被传递给组件Welcome。由于Welcome是一个无状态函数组件,因此它可以访问user的值,如下所示:

const Welcome = (props) => <h1>Hello, {props.user}!</h1>

在处理无状态函数组件时,基本上将props视为一个返回JSX的函数的参数。我们还可以访问函数体中参数的值,但在处理类组件时会有点不同。

下面看一个完整例子:

const CurrentDate = (props) => {
  return (
    <div>
      { /* 在CurrentDate组件中访问属性date,在p标签中显示其值。 */ }
      <p>The current date is:{props.date} </p>
    </div>
  );
};

class Calendar extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <h3>What date is it?</h3>
        { /* 注意,若要date属性的值评估为JavaScript,就必须用花括号括起来 */ }
        <CurrentDate date={Date()} />
      </div>
    );
  }
};
ReactDOM.render(<Calendar/>,document.getElementById('challenge-node'));
{/*DOM页面输出如下: 
What date is it?
The current date is:Mon Oct 17 2022 10:45:37 GMT+0800 (中国标准时间)
*/}

如何将数组作为“props”传递呢?要将数组传递给JSX元素,必须将其视为JavaScript并用花括号括住:

<ParentComponent>
  <ChildComponent colors={["green", "blue", "red"]} />
</ParentComponent>

然后,子组件可以访问数组属性colors,可以使用join()等数组方法访问该属性。"const ChildComponent = (props) => <p>{props.colors.join(', ')}</p> "这将把所有“colors”数组项连接到一个逗号分隔的字符串中,并生成:"<p>green, blue, red</p>" 。看个例子:

const List = (props) => {
  {/*访问List组件中的tasks数组,在p元素中显示其值。使用join(",")将p元素中的props.tasks数组显示为逗号分隔的列表*/}
  return <p>{props.tasks.join(',')}</p>
};

class ToDo extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <h1>To Do Lists</h1>
        <h2>Today</h2>
        {/*给List组件一个tasks属性,并赋值一个数组*/}
        <List tasks={["walk dog", "workout"]}/>
        <h2>Tomorrow</h2>
        <List tasks={["walk dog", "workout","eat"]}/>
      </div>
    );
  }
};
ReactDOM.render(<ToDo/>,document.getElementById("challenge-node"))
{/*DOM页面输出:
To Do Lists
Today
walk dog,workout
Tomorrow
walk dog,workout,eat
 */}

React还可以设置默认props。可以将默认属性作为组件本身的属性指定给组件,如有必要,React将指定默认属性。当没有明确提供值时, 这允许我们指定属性值应该是什么。例如,如果声明"MyComponent.defaultProps = { location: 'San Francisco' }", 则定义了一个设置为字符串“San Francisco”的“location”prop,除非另有指定。如果props未定义,则React会指定默认props,但如果将null作为prop的值传递,它将保持为null。看个例子:

const ShoppingCart = (props) => {
  return (
    <div>
      <h1>Shopping Cart Component</h1>
      <p>默认的"props"为:{props.items}</p>
    </div>
  )
};

ShoppingCart.defaultProps={items:0};

ReactDOM.render(<ShoppingCart/>,document.getElementById("challenge-node"));

{/*DOM页面输出:
Shopping Cart Component
默认的"props"为:0
 */}

React还能设置默认props的值。通过明确的设置组件的prop值可以覆盖默认的props。 

const Items = (props) => {
  return <h1>Current Quantity of Items in Cart: {props.quantity}</h1>
}

Items.defaultProps = {
  quantity: 0
}

class ShoppingCart extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    {/*覆盖quantity属性的默认值*/}
    return <Items quantity={10} />
  }
};
ReactDOM.render(<Items/>,document.getElementById("challenge-node"));
ReactDOM.render(<ShoppingCart/>,document.getElementById("challenge-node"));

{/*页面输出如下:
Current Quantity of Items in Cart: 0
Current Quantity of Items in Cart: 10
*/}

怎么验证组件是否接收到正确类型的props呢?React提供了有用的类型检查语句:在组件上设置propTypes,以要求数据的类型为对应的类型,当数据是任何其他类型时,将会抛出一个警告。用定义defaultProps的相同方式定义组件的propTypes属性。

例如,需要一个名为handleClick的属性的类型函数:

MyComponent.propTypes = { handleClick: PropTypes.func.isRequired }

上例中,“PropTypes.func”检查“handleClick”是否为函数。添加“isRequired”会告诉React“handleClick”是该组件的必需属性。如果没有提供该prop,将会看到一个警告。注意,func表示函数。在七种JavaScript基本类型中,function和boolean(写为bool)是唯一两种使用不寻常拼写的类型。除了基本类型之外,还有其他类型可用。例如,可以检查道具是否为React元素,有关内容请参阅文档:https://reactjs.org/docs/typechecking-with-proptypes.html#proptypes

 再看个例子:

const Items = (props) => {
  return <h1>Current Quantity of Items in Cart: {props.quantity}</h1>
};
{/*使用“PropTypes”定义我们期望的props*/}
Items.propTypes={quantity:PropTypes.number.isRequired};

Items.defaultProps = {
  quantity: 0
};

class ShoppingCart extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <Items />
  }
}; 

注意:从React v15.50开始,PropTypes独立于React导入,如右所示:import PropTypes from 'prop-types';

ES6类组件访问props的方式稍微有点不同。上面的例子都是将props传递给子组件(无状态函数组件),如果要传递prop的子组件是ES6类组件,该怎么办?当我们在类组件内部引用它时,都要使用“this”关键字。要访问类组件中的props,需要在用于访问它的代码前面加上“this”。例如,如果一个ES6类组件有一个名为“data”的属性,那么可以在JSX中编写{this.props.data}

看个例子:

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

  }
  render() {
    return (
        <div>
            { /*给子组件Welcome一个属性名为name的prop*/ }
            <Welcome name='lalala'/>
        </div>
    );
  }
};

class Welcome extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
        <div>
          { /*使用{this.props.name}访问是类组件类型的子组件的props的name*/ }
          <p>Hello, <strong>{this.props.name}</strong>!</p>
        </div>
    );
  }
};
ReactDOM.render(<App/>,document.getElementById('challenge-node'));
ReactDOM.render(<Welcome/>,document.getElementById('challenge-node'));
{/*页面输出:
Hello, lalala! 
Hello, !
*/}  

无状态函数组件是指任何接受props并返回JSX的函数。无状态组件是一个扩展了“React.component”的类,但不使用内部状态(稍后介绍)。

有状态组件是一个类组件,它维护自己的内部状态。有状态组件简称为组件或React组件。

一种常见的模式是尽量减少有状态性,并尽可能创建无状态功能组件。这有助于将状态管理包含到应用程序的特定区域。反过来,这可以通过更容易地了解状态变化如何影响应用程序的行为来改进应用程序的开发和维护。

class CampSite extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <Camper/>
      </div>
    );
  }
};
{/*无状态函数组件Camper:*/} let Camper=(props)=>{ return( <div> <p>{props.name}</p> </div> ) } {/*注意,等号左边的defaultProps和propTypes遵循小驼峰原则,即第二个单词开始首字母大写*/} Camper.defaultProps={name:'CamperBot'}; Camper.propTypes={name:PropTypes.string.isRequired}; //将父组件渲染到DOM: ReactDOM.render(<CampSite/>,document.getElementById('challenge-node')); {/*页面输出: CamperBot */}

通过在React组件的构造函数中声明组件类的“state”属性,可以在该组件中创建状态。当创建组件时,使用state可以对其进行初始化。state属性必须设置为JavaScript对象。state由应用程序需要了解的任何数据组成,这些数据可以随着时间而变化。我们希望应用程序响应状态更改,并在必要时显示更新的UI,React的state为现代web应用程序的状态管理提供了一个很好的解决方案。

在组件的整个生命周期中,我们都可以访问state对象——可以更新它,在UI中呈现它,并将其作为“props”传递给子组件。state对象可以根据需要复杂或简单,注意,为了创建这样的state,必须通过扩展“React.component”来创建类组件。看个例子:

class StatefulComponent extends React.Component {
  constructor(props) {
    super(props);
    {/*声明state如下*/}
    this.state={firstName:'Audrey'};
  }
  render() {
    return (
      <div>
        <h1>{this.state.firstName}</h1>
      </div>
    );
  }
};
ReactDOM.render(<StatefulComponent/>,document.getElementById('challenge-node'));
{/*页面输出如下:
Audrey
 */}

在用户界面如何呈现state对象里的数据呢?

我们定义了组件的初始state后,可以在呈现的UI中显示组件的任何部分。如果组件是有状态的,它将始终可以访问其render()方法中“state”里的数据,使用“this.state”访问数据。如果要访问“render”方法的“return”中的state值,必须将该值括在花括号中。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'freeCodeCamp'
    }
  }
  render() {
    return (
      <div>
        {/*呈现(渲染)组件的state对象里的name值到用户界面(访问组件的state里的数据)*/}
        <h1>{this.state.name}</h1>
      </div>
    );
  }
};
{/*将MyComponent渲染到DOM*/}
ReactDOM.render(<MyComponent/>,document.getElementById('challenge-node'));
{/*DOM输出如下:
freeCodeCamp
 */}

在React中,state是组件最强大的功能之一。它允许我们跟踪应用程序中的重要数据,并呈现更改后的UI以响应此数据的更改,即数据更改,UI就会更改。

React使用所谓的虚拟DOM来跟踪幕后的变化。当state数据更新时,它会触发使用该数据的组件重新呈现(渲染),包括将数据作为props接受的子组件。

React仅在必要时更新实际的DOM。这意味着我们不用去更改DOM,只需声明UI的样子就可以了。

注意,如果将组件设置为有状态,则其他组件都不会知道其“state”。它的“state”是完全封装的,是局部的、本地的,只属于该组件的,除非我们将state数据作为“props”传递给子组件。

封装的“state”概念非常重要,因为它允许我们编写特定的逻辑,然后将该逻辑包含在代码中的一个位置并独立开来。

要访问组件里的state对象的数据,还有另一个方法。

在render()方法中,位于“return”语句之前,直接编写JavaScript。例如,可以声明函数,从“state”或“props”访问数据,对该数据执行计算,等等。然后,可以将任何数据分配给变量,之后在“return”语句中就可以访问这些变量了。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'freeCodeCamp'
    }
  }
  render() {
    //在这部分代码中可以直接编写JavaScript,所以不必用花括号括起“this.state.name”!!
    const name=this.state.name;
    return (
      <div>
        {/*这里直接将上面的变量name拿来用。注意,在return语句中要用JSX语法(JavaScript用花括号括起来)*/}
        <h1>{name}</h1>

      </div>
    );
  }
};
ReactDOM.render(<MyComponent/>,document.getElementById('challenge-node'));
{/*DOM输出如下: freeCodeCamp */} 

如何更改组件的“state”呢?React提供了一种更新组件“state”的方法:setState。在组件类中调用“setState”方法,如“this.setState()”,传递一个带有键值对的对象给它。键是state属性,值是更新的state数据。例如,如果我们在state中存储了一个“username”,要更新它,方法如下:

this.setState({
  username: 'Lewis'
});

看个具体例子:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Initial State'
    };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    //点击按钮的时候把name属性的值改为'React Rocks!'。
    this.setState({name:'React Rocks!'});
  };
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>Click Me</button>
        <h1>{this.state.name}</h1>
      </div>
    );
  }
};
ReactDOM.render(<MyComponent/>,document.getElementById('challenge-node'));
{/*
没点击按钮之前DOM输出:Initial State
点击按钮之后DOM输出:React Rocks!
*/} 

在state产生改变时,React希望我们不要直接修改“state”,而是使用“this.setState()”。此外,要注意,为了提高性能,React可能会批量处理多个状态更新,这意味着通过“setState”方法进行的状态更新可能是异步的。有一种可以替代“setState”的替代语法提供了解决此问题的方法,虽然很少用到,但最好记住它,这里不赘述,更多详细信息参考一篇React文章:https://www.freecodecamp.org/news/what-is-state-in-react-explained-with-examples/

如何为组件类定义方法呢?

类方法通常需要使用“this”关键字,以便访问方法范围内类的属性(如“state”和“props”)。让类方法访问“this”有几种方式:

一种常见的方法是在构造函数中显式绑定“this”,这样在初始化组件时就将“this“绑定到类方法了。上例中,在构造函数中使用了“this.handleClick=this.handleClick.bind(this)”作为其“handleClike”方法。然后,当我们在类方法中调用类似“this.setState()”的函数时,“this”指的是类,不会是“undefined”。(“this”关键字是JavaScript最令人困惑的方面之一,但它在React中扮演着重要角色,要深入理解并掌握它。)

看个例子:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: "Hello"
    };
    //组件初始化时就将handleClick()与this显式绑定(这里的this指当前类,即MyComponent)
    this.handleClick=this.handleClick.bind(this);
  }
  handleClick() {
    this.setState({
      text: "You clicked!"
    });
  }
  render() {
    return (
      <div>
        { /*向button元素添加一个单击处理程序。当按钮接收到单击事件时,它应该触发handleClick()方法。(传递给“onClick”处理程序的
方法需要用花括号括起来,因为它应该直接诠释为JavaScript)*/ } <button onClick={this.handleClick}>Click Me</button> { /*访问text的值*/ } <h1>{this.state.text}</h1> </div> ); } }; {/*将组件MyComponent渲染到DOM*/} ReactDOM.render(<MyComponent/>,document.getElementById('challenge-node')); {/* 没点击按钮之前,页面显示:Hello 点击按钮之后,页面显示:You clicked! */}  

有时,更新state时可能需要知道以前的state。然而,state更新可能是异步的,这意味着React可能会将多个“setState()”调用批处理到单个更新中,即在计算下一个值时不能依赖“This.state”或“This.props”的前一个值。

此时,应该向setState传递一个允许我们访问state和props的函数。使用带有setState的函数可以确保我们使用的是最新的state和props值:

/*错误代码:
this.setState({
  counter: this.state.counter + this.props.increment
});
*/
//正确代码:
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));
//如果只需要“state”,也可以使用不带“props”的形式:
this.setState(state => ({
  counter: state.counter + 1
}));

注意,必须将“=>”后的对象括在括号中,否则JavaScript会认为这是一段代码块。看个具体例子:

要求:如果state里的属性visibility 的上一次值为false则使用方法toggleVisibility()将其设置为true,反之亦然。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      visibility: false
    };
    //将toggleVisibility()方法与this绑定
    this.toggleVisibility=this.toggleVisibility.bind(this);
  }
  toggleVisibility(){
    //"=>"后面的小括号不能省略!必须将对象括在括号中,否则JavaScript会认为这是一段代码块,导致在页面点击按钮时没有反应。
    this.setState((state)=>({
      visibility:state.visibility?false:true
    }));
  };
  render() {
    if (this.state.visibility) {
      return (
        <div>
          <button onClick={this.toggleVisibility}>Click Me</button>
          <h1>Now you see me!</h1>
        </div>
      );
    } else {
      return (
        <div>
          <button onClick={this.toggleVisibility}>Click Me</button>
        </div>
      );
    }
  }
}
ReactDOM.render(<MyComponent/>,document.getElementById('challenge-node'));
/*运行效果:
按钮点击一次时,页面显示:Now you see me!
按钮点击2次时页面不显示文字了。
*/

下面做一个手动计数器:

要求:点击相应的按钮会相应地将count属性加1、减1或重置为0。

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
    this.increment=this.increment.bind(this);
    this.decrement=this.decrement.bind(this);
    this.reset=this.reset.bind(this);
  }
  increment(){
    //注意,setState接受的参数是对象类型!箭头函数是对象,符号“=>”后跟的是花括号括起来的对象键值对形式!这个键值对对象还要用小括号括起来,
以防JavaScript认为这是一段独立的代码块。
this.setState((i)=>({count:i.count+1})); } decrement(){ this.setState((i)=>({count:i.count-1})); } reset(){ this.setState({count:0}); } render() { return ( <div> <button className='inc' onClick={this.increment}>Increment!</button> <button className='dec' onClick={this.decrement}>Decrement!</button> <button className='reset' onClick={this.reset}>Reset</button> <h1>Current Count: {this.state.count}</h1> </div> ); } }; //将组件Counter渲染到DOM ReactDOM.render(<Counter/>,document.getElementById('challenge-node'));

应用程序可能在state和渲染的UI之间有更复杂的交互。例如,文本输入的表单控件元素(如input和textarea)在用户键入时在DOM中维护自己的state。使用React,可以将此可变state移动到React组件的state。用户的输入成为应用程序state的一部分,因此React控制该输入框的值。如果一个React组件带有用户可以键入的输入框,那么它就是一个受控输入表单,换句话说,React可以控制某些元素(如input和textarea)的内部状态,从而使它们成为受控组件:

class ControlledInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: ''
    };
    //在构造函数中将handleChange()与this绑定起来
    this.handleChange=this.handleChange.bind(this);
  }
  handleChange(i){
    //i.target.value可以访问用户在input里输入的值
    this.setState({input:i.target.value});
  }
  render() {
    return (
      <div>
        { /*下面的表单里的属性值表达式要用花括号括起来,意为JS代码*/}
        <input value={this.state['input']} onChange={this.handleChange}/>
        <h4>Controlled Input:</h4>
        <p>{this.state.input}</p>
      </div>
    );
  }
};
//将组件ControlledInput渲染到DOM
ReactDOM.render(<ControlledInput/>,document.getElementById('challenge-node'));

用户在输入框中输入内容时,内容经handleChange()方法处理,在本地state下设置为input属性,并在页面的输入框中呈现为值。组件的“state”是“input”数据的唯一真实来源。

受控组件也适用于其他表单元素,包括常规HTML的form元素。看个例子:

要求:在input输入内容后,按“enter”或点击button按钮都能使页面呈现组件state里submit属性的值。

class MyForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: '',
      submit: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleChange(event) {
    this.setState({
      input: event.target.value
    });
  }
  handleSubmit(event) {
    //本地state的属性submit的值=属性input的值
    this.setState({submit:this.state.input});
    //在提交处理程序中调用“event.proventDefault()”,以防止会刷新网页的默认表单提交行为。
    event.preventDefault();
  }
  render() {
    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <input value={this.state.input} onChange={this.handleChange} />
          {/*由于button在form元素里,故点击按钮可以触发form的onSubmit处理程序*/}
          <button type='submit'>Submit!</button>
        </form>
        
        {/*h1呈现组件state的submit属性的值*/}
        <h1>{this.state.submit}</h1>
      </div>
    );
  }
}
//将组件MyForm渲染到DOM
ReactDOM.render(<MyForm/>,document.getElementById('challenge-node'));

前面有很多将props传递给子JSX元素和子React组件的示例。这些props是从哪里来的呢?一种常见的模式是,有一个有状态组件,其中包含对应用程序重要的“state”,然后渲染子组件。我们希望这些子组件能够访问该“state”的某些部分,这些部分作为props传入。

例如,假设我们有一个“App”组件,用来渲染“Navbar”等其他子组件。在“App”中,有包含大量用户信息的“state”,但“Navbar”只需要访问用户的username以便显示它,我们就可以将该“state”的username作为props传递给“Navbar”组件。

这个模式说明了React的一些重要典范第一,单向数据流。state沿着应用程序组件树的一个方向向下流动,从有状态父组件流向子组件。子组件只接收它们需要的state数据。第二,复杂的有状态应用程序可以分解为几个甚至是单个有状态组件。其余组件只需从父组件接收state作为props,并从该state渲染用户界面。然后开始创建一个分离:state管理在代码的一部分中处理,UI渲染则在另一部分处理。将state逻辑与UI逻辑分离的规范法则是React的关键法则之一,当它被正确使用时,可以使复杂的、有状态的应用程序的布局更容易管理。

看下如何将state作为props传递给子组件:

class MyApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'CamperBot'
    }
  }
  render() {
    return (
       <div>
         {/*引用子组件时,在子组件处建立一个name属性储存父组件state的name属性值*/}
         <Navbar name={this.state.name}/>        
       </div>
    );
  }
};

class Navbar extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
    <div>
      {/*因为我们将父组件state的属性name的值传入了上面的NavBar组件,Navbar.props现在有了内容:“name=CamperBot”,下面的h1元素将呈现
从state传递过来的值CamperBot
*/} <h1>Hello, my name is:{this.props.name} </h1> </div> ); } }; //将组件MyApp渲染到DOM ReactDOM.render(<MyApp/>,document.getElementById('challenge-node')); /*页面显示: Hello, my name is:CamperBot */

我们还可以将处理程序函数或在React组件上定义的任何方法传递给子组件,这是允许子组件与其父组件交互的方式。那么React组件之间如何将函数作为props传递给子组件呢?其实也是和传递state数据一样的方法:方法被分配一个名称(我们自定义的名字),然后我们可以在子组件的“this.props”下访问该方法名称。

看个例子:

要求:能够在GetInput组件的“input”文本框输入内容,然后通过props调用了其父组件中的handleChange方法,这将会更新父级“state”中的input属性值(input已经作为props传递给了两个子组件)。

class MyApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      inputValue: ''
    }
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(event) {
    this.setState({
      inputValue: event.target.value
    });
  }
  render() {
    return (
       <div>
        {/*定义input、handleChange并分别赋值state数据和函数*/}
        <GetInput input={this.state.inputValue} handleChange={this.handleChange} />
        <RenderInput input={this.state.inputValue} /> 
       </div>
    );
  }
};
class GetInput extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <h3>Get Input:</h3>
        <input
          value={this.props.input}
          onChange={this.props.handleChange}/>
      </div>
    );
  }
};
class RenderInput extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <h3>Input Render:</h3>
        <p>{this.props.input}</p>
      </div>
    );
  }
};
//将父组件MyApp渲染到DOM即可:
ReactDOM.render(<MyApp/>,document.getElementById('challenge-node'));  

。。。

posted @ 2022-10-14 11:11  枭二熊  阅读(20)  评论(0编辑  收藏  举报