陌小北的头像

前端开发:React入门(二)

单集封面

前端开发:React入门(二)

2022-07-13
33 次观看
陌小北的头像
陌小北
粉丝:10
主题:15
描述:25
例子:21
类比:1
其他:8
字数:11663

前端开发:React入门(二)

2022-07-13
33 次观看
陌小北的头像
陌小北
粉丝:10
陌小北的头像
陌小北
粉丝:10
主题:15
描述:25
例子:21
类比:1
其他:8
字数:11663

事件处理

与传统DOM的差异

1、语法差异

React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同: 1.React 事件的命名采用小驼峰式(camelCase),而不是纯小写。 2.使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。

传统DOM与React区别

传统html:

<button onclick="activateLasers()">
  Activate Lasers
</button>

react:

<button onClick={activateLasers}>  Activate Lasers
</button>
2、阻止默认行为

在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。 你必须显式的使用 preventDefault。

阻止默认行为示例

例如,传统的 HTML 中阻止表单的默认提交行为,你可以这样写:

<form onsubmit="console.log('You clicked submit.'); return false">
  <button type="submit">Submit</button>
</form>

在 React 中,可能是这样的:

function Form() {
  // 在这里,e 是一个合成事件。
  function handleSubmit(e) {
    e.preventDefault();    
    console.log('You clicked submit.');
  }
  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
  );
}
拓展 遵循规范合成事件

React 根据 W3C 规范来定义这些合成事件,所以你不需要担心跨浏览器的兼容性问题。

拓展 与原生事件的差异

React 事件与原生事件不完全相同。如果想了解更多,请查看 SyntheticEvent 参考指南。

初始渲染

使用 React 时,你一般不需要使用 addEventListener 为已创建的 DOM 元素添加监听器。 事实上,你只需要在该元素初始渲染的时候添加监听器即可。

##实现

声明为class的方法

当你使用 ES6 class 语法定义一个组件的时候,通常的做法是将事件处理函数声明为 class 中的方法。

切换开关

例如,下面的 Toggle 组件会渲染一个让用户切换开关状态的按钮:

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>
    );
  }
}
注意this使用

你必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。 如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined。

React与JavaStript

这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。通常情况下,如果你没有在方法后面添加 (),例如 onClick={this.handleClick},你应该为这个方法绑定 this。

替换绑定方式

第一种替换方式

如果觉得使用 bind 很麻烦,这里有两种方式可以解决。你可以使用 public class fields 语法 来快速完成绑定:

替换方式一
class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.  
   handleClick = () => {   
     console.log('this is:', this);  };  
     render() {
       return (
          <button onClick={this.handleClick}>
           Click me
          </button>
    );
  }
}
第二种替换方式

如果你没有使用 class fields 语法,你可以在回调中使用箭头函数:

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

  render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。    
     return (      
     <button onClick={() => this.handleClick()}>        
         Click me
     </button>
    );
  }
}
第二种替换的弊端

此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。

向事件处理程序传递参数

常识 事件处理传参

在循环中,通常我们会为事件处理函数传递额外的参数。

事件处理传参示例

例如,若 id 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:

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

上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind 来实现。

在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。 如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

条件渲染

支持部分渲染

在 React 中,你可以创建不同的组件来封装各种你需要的行为。 然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。

与JavaScript一致

React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。

渲染问候语
function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

再创建一个 Greeting 组件,它会根据用户是否登录来决定显示上面的哪一个组件。

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {    return <UserGreeting />;  }  return <GuestGreeting />;}
const root = ReactDOM.createRoot(document.getElementById('root')); 
// Try changing to isLoggedIn={true}:
root.render(<Greeting isLoggedIn={false} />);

元素变量

元素变量的作用

你可以使用变量来储存元素。 它可以帮助你有条件地渲染组件的一部分,而其他的渲染部分并不会因此而改变。

登录和注销

观察这两个组件,它们分别代表了注销和登录按钮:

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

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

创建一个名叫 LoginControl 的有状态的组件。 它将根据当前的状态来渲染 <LoginButton /> 或者 <LogoutButton />。 同时它还会渲染上一个示例中的 <Greeting />。

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

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

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

  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    if (isLoggedIn) {      button = <LogoutButton onClick={this.handleLogoutClick} />;    } else {      button = <LoginButton onClick={this.handleLoginClick} />;    }
    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />        {button}      </div>
    );
  }
}

const root = ReactDOM.createRoot(document.getElementById('root')); 
root.render(<LoginControl />);

内联条件渲染

引出 简单渲染

声明一个变量并使用 if 语句进行条件渲染是不错的方式,但有时你可能会想使用更为简洁的语法。

与运算符 &&

嵌入表达式

通过花括号包裹代码,你可以在 JSX 中嵌入表达式。 这也包括 JavaScript 中的逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染:

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

const messages = ['React', 'Re: React', 'Re:Re: React'];

const root = ReactDOM.createRoot(document.getElementById('root')); 
root.render(<Mailbox unreadMessages={messages} />);
JavaScript中&&的特性

之所以能这样做,是因为在 JavaScript 中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。 因此,如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。

falsy表达式

请注意,falsy 表达式 会使 && 后面的元素被跳过,但会返回 falsy 表达式的值。在下面示例中,render 方法的返回值是 <div>0</div>。

falsy表达式示例
render() {
  const count = 0;  return (
    <div>
      {count && <h1>Messages: {count}</h1>}    </div>
  );
}

三目运算符

三目运算符

另一种内联条件渲染的方法是使用 JavaScript 中的三目运算符 condition ? true : false。

三目运算符示例

在下面这个示例中,我们用它来条件渲染一小段文本

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is 
      <b>{
          isLoggedIn ? 'currently' : 'not'}
       </b> logged in.    
     </div>
  );
}
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />      }
    </div>  );
}

阻止组件渲染

隐藏组件

在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。 若要完成此操作,你可以让 render 方法直接返回 null,而不进行任何渲染。

隐藏组件示例

下面的示例中,<WarningBanner /> 会根据 prop 中 warn 的值来进行条件渲染。 如果 warn 的值是 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};
    this.handleToggleClick = this.handleToggleClick.bind(this);
  }

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

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

const root = ReactDOM.createRoot(document.getElementById('root')); 
root.render(<Page />);

在组件的 render 方法中返回 null 并不会影响组件的生命周期。 例如,上面这个示例中,componentDidUpdate 依然会被调用。

列表&Key

JavaScript转化列表

如下代码,我们使用 map() 函数让数组中的每一项变双倍,然后我们得到了一个新的列表 doubled 并打印出来:

JavaScript转化列表示例
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);console.log(doubled);

代码打印出 [2, 4, 6, 8, 10]。

过渡 React转化列表

在 React 中,把数组转化为元素列表的过程是相似的。

多组件渲染

渲染多个组件

你可以通过使用 {} 在 JSX 内构建一个元素集合。

渲染多个组件示例

下面,我们使用 Javascript 中的 map() 方法来遍历 numbers 数组。 将数组中的每个元素变成 <li> 标签,最后我们将得到的数组赋值给 listItems:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>  <li>{number}</li>);

然后,我们可以将整个 listItems 插入到 <ul> 元素中:

<ul>{listItems}</ul>

这段代码生成了一个 1 到 5 的项目符号列表。

基础列表组件

组件中渲染列表

通常你需要在一个组件中渲染列表。

组件中渲染列表示例

我们可以把前面的例子重构成一个组件,这个组件接收 numbers 数组作为参数并输出一个元素列表。

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>    <li>{number}</li>  );  return (
    <ul>{listItems}</ul>  );
}

const numbers = [1, 2, 3, 4, 5];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<NumberList numbers={numbers} />);
说明 运行出现警告

当我们运行这段代码,将会看到一个警告 a key should be provided for list items,意思是当你创建一个元素时,必须包括一个特殊的 key 属性。 我们将在下一节讨论这是为什么。

修复警告

让我们来给每个列表元素分配一个 key 属性来解决上面的那个警告:

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>      
        {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

key

key的作用

key 帮助 React 识别哪些元素改变了,比如被添加或删除。 因此你应当给数组中的每一个元素赋予一个确定的标识。

key示例
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>    {number}
  </li>
);

const todoItems = todos.map((todo) =>
  <li key={todo.id}>    {todo.text}
  </li>
);
唯一字符串

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。

id当做key

通常,我们使用数据中的 id 来作为元素的 key:

id当做key示例
const todoItems = todos.map((todo) =>
  <li key={todo.id}>    {todo.text}
  </li>
);
索引当做key

当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key:

索引当做key示例
const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs  <li key={index}>    {todo.text}
  </li>
);
不建议用索引的情况

如果列表项目的顺序可能会变化,我们不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题。

拓展 索引当key的负面影响

可以看看 Robin Pokorny 的深度解析使用索引作为 key 的负面影响这一篇文章。

不指定显式key值

如果你选择不指定显式的 key 值,那么 React 将默认使用索引用作为列表项目的 key 值。

拓展 分析key的必要性

要是你有兴趣了解更多的话,这里有一篇文章深入解析为什么 key 是必须的可以参考。

用key提取组件

放在数组上下文

元素的 key 只有放在就近的数组上下文中才有意义。

提取ListItem组件

比方说,如果你提取出一个 ListItem 组件,你应该把 key 保留在数组中的这个 <ListItem /> 元素上, 而不是放在 ListItem 组件中的 <li> 元素上

错误方式
function ListItem(props) {
  const value = props.value;
  return (
    // 错误!你不需要在这里指定 key:    <li key={value.toString()}>      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 错误!元素的 key 应该在这里指定:    
    <ListItem value={number} />  
   );
  return (
    <ul>
      {listItems}
    </ul>
  );
}
正确方式
function ListItem(props) {
  // 正确!这里不需要指定 key:  
  return <li>{props.value}</li>;}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 正确!key 应该在数组的上下文中被指定    
    <ListItem key={number.toString()} value={number} />  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}
讨论
随记