React学习(二)

81 阅读11分钟

内容

参考

  1. React Props
  2. React 事件处理
  3. React 条件渲染
  4. React 列表&Keys

1,Props

state与props主要的区别在于props是不可变的,而state可以根据与用户交互来改变。这就是为什么有些容器组件需要定义state来更新和修改数据。而子组件只能通过props来传递数据。

默认Props

可以通过组件类的defaultProps属性设置props的默认值,示例:

class HelloMessage extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

HelloMessage.defaultProps = {
  name: 'Runoob'
};

const element = <HelloMessage/>;

ReactDOM.render(
  element,
  document.getElementById('example')
);

State与Props

示例:可以在父组件中设置state,并通过在子组件上使用props将其传递到子组件上,在render() 函数中,设置name和site来获取父组件传递过来的数据。

class WebSite extends React.Component {
  constructor() {
      super();

      this.state = {
        name: "菜鸟教程",
        site: "https://www.runoob.com"
      }
    }
  render() {
    return (
      <div>
        <Name name={this.state.name} />
        <Link site={this.state.site} />
      </div>
    );
  }
}



class Name extends React.Component {
  render() {
    return (
      <h1>{this.props.name}</h1>
    );
  }
}
 
class Link extends React.Component {
  render() {
    return (
      <a href={this.props.site}>
        {this.props.site}
      </a>
    );
  }
}
 
ReactDOM.render(
  <WebSite />,
  document.getElementById('example')
);

代码解释:父组件WebSite中,向子组件(Name、Link)分别传入了this.state.namethis.state.site参数。子组件在render()方法中获取传递的值并设置。

Props验证

注意:React.PropTypes 在 React v15.5 版本后已经移到了 prop-types 库。

Props验证使用propTypes,它可以保证我们的应用组件被正确使用,React.PropTypes提供了许多验证器(validator)来验证传入数据是否有效。当向props传入无效数据时,JavaScript控制台会抛出警告。示例:

var title = "菜鸟教程";
// var title = 123;
class MyTitle extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.title}</h1>
    );
  }
}

MyTitle.propTypes = {
  title: PropTypes.string
};
ReactDOM.render(
    <MyTitle title={title} />,
    document.getElementById('example')
);

代码解释:该示例创建了一个MyTitle组件,属性title必须是string,非字符串会自动转为字符串。

更多组件说明:(在MyComponent.propTypes内)

  1. 可以声明prop为指定的JavaScript基本数据类型,默认情况下,这些数据是可选的
optionalArray: React.PropTypes.array,
optionalBool: React.PropTypes.bool, 
optionalFunc: React.PropTypes.func, 
optionalNumber: React.PropTypes.number,
optionalObject: React.PropTypes.object,
optionalString: React.PropTypes.string,
  1. 可以被渲染的对象 numbers, strings, elements 或 array。
optionalNode: React.PropTypes.node,
  1. React 元素
optionalElement: React.PropTypes.element,
  1. 用 JS 的 instanceof 操作符声明 prop 为类的实例。
optionalMessage: React.PropTypes.instanceOf(Message),
  1. 用 enum 来限制 prop 只接受指定的值
optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
  1. 可以是多个对象类型中的一个
optionalUnion: React.PropTypes.oneOfType([ 
    React.PropTypes.string, 
    React.PropTypes.number,
    React.PropTypes.instanceOf(Message) 
]),
  1. 指定类型组成的数组
optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
  1. 指定类型的属性构成的对象
optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
  1. 特定 shape 参数的对象
optionalObjectWithShape: React.PropTypes.shape({
    color: React.PropTypes.string, 
    fontSize: React.PropTypes.number 
}),
  1. 任意类型加上 isRequired 来使 prop 不可空。
requiredFunc: React.PropTypes.func.isRequired,
  1. 不可为空的任意类型
requiredAny: React.PropTypes.any.isRequired,
  1. 自定义验证器。如果验证失败需要返回一个 Error 对象。不要直接使用 console.warn 或抛异常,因为这样 oneOfType 会失效。
customProp: function(props, propName, componentName) { 
    if (!/matchme/.test(props[propName])) { 
        return new Error('Validation failed!'); 
    } 
}

2,事件处理

React元素的事件处理与DOM元素类似,但是有一点语法上的不同:

  • React事件绑定属性的命名采用驼峰式命名,而不是小写。
  • 如果采用JSX的语法,需要传入一个函数作为事件处理函数,而不是一个字符串(DOM元素的写法)。

HTML通常的写法:

<button onclick="activateLasers()">
  激活按钮
</button>

React中的写法:

<button onClick={activateLasers}>
  激活按钮
</button>

在React中另一个不同是:不能使用返回false的方式阻止默认行为。必须明确使用preventDefault,例如:在HTML中阻止链接默认打开一个新页面

<a href="#" onclick="console.log('点击链接'); return false">
  点我
</a>

在React中的写法为:其中e为一个合成事件。

function ActionLink() { 
    function handleClick(e) { 
        e.preventDefault();
        console.log('链接被点击'); 
    } 
    return ( 
        <a href="#" onClick={handleClick}>
            点我
        </a> 
    ); 
}

使用React时,通常不需要使用addEventListener为一个已创建的DOM元素添加监听器,仅需要在这个元素初始渲染时提供一个监听器。当使用ES6 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>
    );
  }
}

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

注意:必须谨慎对待JSX回调函数中的this,类的方法默认是不会绑定this的。例如:如果忘记绑定this.handleClick,但却把它传入onClick,当调用这个函数时this的值会是undefined。 这并不是React的特殊行为,它是函数如何在JavaScript中运行的一部分。通常情况下,如果没有在方法后面添加(),例如onClick={this.handleClick},你应该为这个方法绑定 this

如果觉得使用bind很麻烦,有两种解决方式。

  • 如果你正在使用实验性的属性初始化器语法,你可以使用属性初始化器来正确的绑定回调函数:
class LoggingButton extends React.Component {
    // 这个语法确保了 `this` 绑定在 handleClick 中
    // 这里只是一个测试
    handleClick = () => {
        console.log('this is:', this); 
    } 
    render() { 
        return (
            <button onClick={this.handleClick}>
                Click me
            </button> 
        );
    } 
}
  • 如果你没有使用属性初始化器语法,你可以在回调函数中使用 箭头函数
class LoggingButton extends React.Component { 
    handleClick() { 
        console.log('this is:', this); 
    } 
    render() { 
        // 这个语法确保了 `this` 绑定在 handleClick 中 
        return ( 
            <button onClick={(e) => this.handleClick(e)}>
                Click me 
            </button> 
        ); 
    } 
}

说明:使用这个语法有个问题就是每次 LoggingButton 渲染的时候都会创建一个不同的回调函数。在大多数情况下,这没有问题。然而如果这个回调函数作为一个属性值传入低阶组件,这些组件可能会进行额外的重新渲染。我们通常建议在构造函数中绑定或使用属性初始化器语法来避免这类性能问题

向事件处理程序传递参数

通常会为事件处理程序传递额外的参数,例如,若是 id 是你要删除那一行的 id,以下两种方式都可以向事件处理程序传递参数:

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

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

注意:值得注意的是,通过 bind 方式向监听函数传参,在类组件中定义的监听函数,事件对象 e 要排在所传递参数的后面,示例:

class Popper extends React.Component{
    constructor(){
        super();
        this.state = {name:'Hello world!'}; 
    } 
    preventPop(name, e){ //事件对象e要放在最后 
        e.preventDefault();
        alert(name); 
    } 
    render(){ 
        return ( 
            <div> 
                <p>hello</p> 
                {/* 通过 bind() 方法传递参数。 */} 
                <a href="https://reactjs.org" onClick={this.preventPop.bind(this,this.state.name)}>Click</a> 
            </div> 
        ); 
    } 
}

3,条件渲染

在React中,可以创建不同的组件来封装各种你需要的行为,然后还可以根据应用的状态变化只渲染其中的一部分,React中的条件渲染和JavaScript中的一致,使用JavaScript操作符if或者条件运算木来创建表示当前状态的元素,然后根据它们来更新UI。

示例:以下是两个组件

function UserGreeting(props) {
  return <h1>欢迎回来!</h1>;
}

function GuestGreeting(props) {
  return <h1>请先注册。</h1>;
}

创建一个Greeting组件,它会根据用户是否登录来显示其中之一。

function UserGreeting(props) {
  return <h1>欢迎回来!</h1>;
}

function GuestGreeting(props) {
  return <h1>请先注册。</h1>;
}

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('example')
);

方式一:元素变量

可以使用变量来储存元素,它可以帮助你有条件的渲染组件的一部分,而输出的其他部分不会更改。 示例:创建一个名为 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>
    );
  }
}

function UserGreeting(props) {
  return <h1>欢迎回来!</h1>;
}

function GuestGreeting(props) {
  return <h1>请先注册。</h1>;
}

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

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

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

ReactDOM.render(
  <LoginControl />,
  document.getElementById('example')
);

方式二:与运算符&&

可以通过使用花括号包裹代码在JSX中嵌入任何表达式,也包括JavaScript的逻辑与&&,它可以方便地条件渲染一个元素。

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          您有 {unreadMessages.length} 条未读信息。
        </h2>
      }
    </div>
  );
}

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

代码解释:在 JavaScript 中,true && expression 总是返回 expression,而 false && expression 总是返回 false。因此,如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。

方式三:三目运算符

条件渲染的另一种方法是使用 JavaScript 的条件运算符:condition ? true : false。示例:在下面的例子中,我们用它来有条件的渲染一小段文本。 render() { const isLoggedIn = this.state.isLoggedIn; return ( The user is  {isLoggedIn ? 'currently' : 'not'} logged in. ); } 同样它也可以用在较大的表达式中,虽然不太直观:

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
}

方式四:阻止组件渲染

在极少数情况下,你可能希望隐藏组件,即使它被其他组件渲染。让 render 方法返回 null 而不是它的渲染结果即可实现。示例:在下面的例子中,<WarningBanner /> 根据属性 warn 的值条件渲染。如果 warn 的值是 false,则组件不会渲染:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 实例</title>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<style>
button {
  height: 40px;
  width: 200px;
}
.warning {
  background-color: red;
  text-align: center;
  width: 100%;
  padding: 10px;

  font-size: 14pt;
  color: white;
}
</style>
</head>
<body>
<div id="example"></div>

<script type="text/babel">
function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      警告!
    </div>
  );
}

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

  handleToggleClick() {
    this.setState(prevState => ({
      showWarning: !prevState.showWarning
    }));
  }
  
  render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? '隐藏' : '显示'}
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <Page />,
  document.getElementById('example')
);
</script>

</body>
</html>

代码解释:组件的 render 方法返回 null 并不会影响该组件生命周期方法的回调。例如,componentWillUpdatecomponentDidUpdate 依然可以被调用。

4,列表&Keys

可以使用JavaScript的map()方法来创建列表。实例:使用 map() 方法遍历数组生成了一个 1 到 5 的数字列表:

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

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('example')
);

可将该实例重构成一个组件,组件接收数组参数,每个列表元素分配一个key,不然会出现警告a key should be provided for list items,意思是要包含key。

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

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('example')
);

Keys

Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。

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

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用来自数据的 id 作为元素的 key:

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

当元素没有确定的 id 时,你可以使用他的序列号索引 index 作为 key

const todoItems = todos.map((todo, index) =>
  // 只有在没有确定的 id 时使用
  <li key={index}>
    {todo.text}
  </li>
);

说明:如果列表可以重新排序,我们不建议使用索引来进行排序,因为这会导致渲染变得很慢。

用keys提取组件

元素的 key 只有在它和它的兄弟节点对比时才有意义。比方说,如果你提取出一个 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>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('example')
);

正确示例:

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>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('example')
);

说明:当你在 map() 方法的内部调用元素时,你最好随时记得为每一个元素加上一个独一无二的 key。

元素的 key 在他的兄弟元素之间应该唯一

数组元素中使用的 key 在其兄弟之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的键。示例:

function Blog(props) {
  const sidebar = (
    <ul>
      {props.posts.map((post) =>
        <li key={post.id}>
          {post.title}
        </li>
      )}
    </ul>
  );
  const content = props.posts.map((post) =>
    <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  );
  return (
    <div>
      {sidebar}
      <hr />
      {content}
    </div>
  );
}

const posts = [
  {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
  {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
  <Blog posts={posts} />,
  document.getElementById('example')
);

key 会作为给 React 的提示,但不会传递给你的组件。如果您的组件中需要使用和 key 相同的值,请将其作为属性传递:

const content = posts.map((post) =>
  <Post
    key={post.id}
    id={post.id}
    title={post.title} />
);

代码解释:上面例子中,Post 组件可以读出 props.id,但是不能读出 props.key。

在 jsx 中嵌入 map()

在上面的例子中,我们声明了一个单独的 listItems 变量并将其包含在 JSX 中:

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

  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

JSX 允许在大括号中嵌入任何表达式,所以我们可以在 map() 中这样使用:

function ListItem(props) {
  return <li>{props.value}</li>;
}

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

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('example')
);

注意:这么做有时可以使你的代码更清晰,但有时这种风格也会被滥用。就像在 JavaScript 中一样,何时需要为了可读性提取出一个变量,这完全取决于你。但请记住,如果一个 map() 嵌套了太多层级,那你就可以提取出组件。