React文档笔记

作者: 琪先生_zZ | 来源:发表于2018-05-17 17:22 被阅读127次

    jsx

    const element = <h1>Hello, world!</h1>;
    这就是 JSX ,他是 JavaScrip 的一种扩展语法。我们推荐在 React 中使用这种语法来描述 UI 信息。JSX 可能会让你想起某种模板语言,但是它具有 JavaScrip 的全部能力。

    JSX 表示对象

    • Babel 将JSX编译成 React.createElement() 调用。
    • 下面的两个例子是是完全相同的:
    const element = (
      <h1 className="greeting">
        Hello, world!
      </h1>
    );
    
    const element = React.createElement(
      'h1',
      {className: 'greeting'},
      'Hello, world!'
    );
    

    React 只更新必需要更新的部分

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

    组件(Components) 和 属性(Props)

    愚人码头注:Props , 即属性(Property), 在代码中写作 props , 故可用 props 指代 properties .

      1. 函数式组件
      1. 类组件

    警告:
    组件名称总是以大写字母开始。
    举例来说, <div /> 代表一个 DOM 标签,而 <Welcome /> 则代表一个组件,并且需要在作用域中有一个 Welcome 组件。

    提取组件

    • 不要害怕把一个组件分为多个更小的组件。
    • 组件化思维

    Props 是只读的

    • “纯函数”
      因为它们不会试图改变它们的输入,并且对于同样的输入,始终可以得到相同的结果。
    • 所有 React 组件都必须是纯函数,并禁止修改其自身 props 。

    状态(State) 和 生命周期

    example:

    function Clock(props) {
      return (
        <div>
          <h1>Hello, world!</h1>
          <h2>It is {props.date.toLocaleTimeString()}.</h2>
        </div>
      );
    }
    
    function tick() {
      ReactDOM.render(
        <Clock date={new Date()} />,
        document.getElementById('root')
      );
    }
    
    setInterval(tick, 1000);
    
    使用state改进

    state 和 props 类似,但是它是私有的,并且由组件本身完全控制。

    • 把函数式组件转化为类组件:
    1. 创建一个继承自 React.Component 类的 ES6 class 同名类。
    2. 添加一个名为 render() 的空方法。
    3. 把原函数中的所有内容移至 render() 中。
    4. render() 方法中使用 this.props 替代 props
    5. 删除保留的空函数声明。
    class Clock extends React.Component {
      render() {
        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
          </div>
        );
      }
    }
    
    class Clock extends React.Component {
      constructor(props) {
        super(props);
        this.state = {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')
    );
    
    • 在类中添加生命周期方法
    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(状态)

      1. 不要直接修改 state(状态)
    // 错误
    this.state.comment = 'Hello';
    
    // 正确
    this.setState({comment: 'Hello'});
    
      1. state(状态) 更新可能是异步的
        React 为了优化性能,有可能会将多个 setState() 调用合并为一次更新。
        因为 this.props 和 this.state 可能是异步更新的,你不能依赖他们的值计算下一个state(状态)。
    例如, 以下代码可能导致 counter(计数器)更新失败:
    // 错误
    this.setState({
      counter: this.state.counter + this.props.increment,
    });
    

    要弥补这个问题,使用另一种 setState() 的形式,它接受一个函数而不是一个对象。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数:

    // 正确
    this.setState((prevState, props) => ({
      counter: prevState.counter + props.increment
    }));
    

    state(状态)更新会被合并

    当你调用 setState(), React 将合并你提供的对象到当前的状态中。

     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 的值。

    数据向下流动

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


    处理事件

    • 取消点击默认行为

    React 中你不能通过返回 false(愚人码头注:即 return false; 语句) 来阻止默认行为。必须明确调用 preventDefault

    function ActionLink() {
      function handleClick(e) {
        // e 无兼容性问题
        e.preventDefault();
        console.log('The link was clicked.');
      }
    
      return (
        <a href="#" onClick={handleClick}>
          Click me
        </a>
      );
    }
    

    this的绑定及事件参数传递

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

    述两行代码是等价的,分别使用 arrow functionsFunction.prototype.bind


    条件渲染

    • 有状态组件
      它将渲染<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 = null;
        if (isLoggedIn) {
          button = <LogoutButton onClick={this.handleLogoutClick} />;
        } else {
          button = <LoginButton onClick={this.handleLoginClick} />;
        }
    
        return (
          <div>
            <Greeting isLoggedIn={isLoggedIn} />
            {button}
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <LoginControl />,
      document.getElementById('root')
    );
    

    使用逻辑 && 操作符的内联 if 用法

    您可以 在JSX中嵌入任何表达式 ,方法是将其包裹在花括号中。这也包括 JavaScript 逻辑 && 运算符。

    使用条件操作符的内联 If-Else

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

    防止组件渲染

    • 在极少数情况下,您可能希望组件隐藏自身,即使它是由另一个组件渲染的。为此,返回 null 而不是其渲染输出。
    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(prevState => ({
          showWarning: !prevState.showWarning
        }));
      }
    
      render() {
        return (
          <div>
            <WarningBanner warn={this.state.showWarning} />
            <button onClick={this.handleToggleClick}>
              {this.state.showWarning ? 'Hide' : 'Show'}
            </button>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Page />,
      document.getElementById('root')
    );
    

    列表(Lists) 和 键(Keys)

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

    example:

    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];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    );
    
    • 当运行上述代码的时候,将会收到一个警告:a key should be provided for list items(应该为列表元素提供一个键)(愚人码头注 :CodeOpen 中没有报警告,是因为其示例中使用的是 min 版本的 React,换成非 min 版本的就可以看到)。

    • 解决丢失 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('root')
    );
    

    键(Keys)

    • 键(Keys) 帮助 React 标识哪个项被修改、添加或者移除了。数组中的每一个元素都应该有一个唯一不变的键(Keys)来标识,
    • 挑选 key 最好的方式是使用一个在它的同辈元素中不重复的标识字符串。多数情况你可以使用数据中的 IDs 作为 keys:
    const todoItems = todos.map((todo) =>
      <li key={todo.id}>
        {todo.text}
      </li>
    );
    
    • 当要渲染的列表项中没有稳定的 IDs 时,你可以使用数据项的索引值作为 key 的最后选择:
    const todoItems = todos.map((todo, index) =>
      // Only do this if items have no stable IDs
      <li key={index}>
        {todo.text}
      </li>
    );
    

    如果列表项可能被重新排序时,我们不建议使用索引作为 keys,因为这导致一定的性能问题,会很慢。

    使用 keys 提取组件

    • 错误的 key 用法
    • 一个好的经验准则是元素中调用 map() 需要 keys 。
    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('root')
    );
    

    keys 在同辈元素中必须是唯一的

    • 在数组中使用的 keys 必须在它们的同辈之间唯一。然而它们并不需要全局唯一。
    • 键是React的一个内部映射,但其不会传递给组件的内部。如果你需要在组件中使用相同的值,可以明确使用一个不同名字的 prop 传入。
    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使用大括号
    function NumberList(props) {
      const numbers = props.numbers;
      return (
        <ul>
          {numbers.map((number) =>
            <ListItem key={number.toString()}
                      value={number} />
    
          )}
        </ul>
      );
    }
    

    表单(Forms)

    • select 标签

    在 HTML 中,<select> 创建了一个下拉列表。例如,这段 HTML 创建一个下拉的口味(flavors)列表:

    <select>
      <option value="grapefruit">Grapefruit</option>
      <option value="lime">Lime</option>
      <option selected value="coconut">Coconut</option>
      <option value="mango">Mango</option>
    </select>
    

    注意,Coconut 选项是初始化选中的,因为它的 selected 属性。React 中,并不使用这个 selected 属性,而是在根 select 标签中使用了一个 value 属性。这使得受控组件使用更方便,因为你只需要更新一处即可。例如:

    class FlavorForm extends React.Component {
      constructor(props) {
        super(props);
        this.state = {value: 'coconut'};
    
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
    
      handleChange(event) {
        this.setState({value: event.target.value});
      }
    
      handleSubmit(event) {
        alert('Your favorite flavor is: ' + this.state.value);
        event.preventDefault();
      }
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Pick your favorite La Croix flavor:
              <select value={this.state.value} onChange={this.handleChange}>
                <option value="grapefruit">Grapefruit</option>
                <option value="lime">Lime</option>
                <option value="coconut">Coconut</option>
                <option value="mango">Mango</option>
              </select>
            </label>
            <input type="submit" value="Submit" />
          </form>
        );
      }
    }
    
    • 总的来说,这使 <input type="text">, <textarea> 和 <select> 都以类似的方式工作 —— 它们都接受一个 value 属性可以用来实现一个受控组件。

    file input 标签

    <input type="file" />
    在 React 中,一个 <input type =“file”/> 和一个普通的 <input /> 类似,但有一个重要的区别:它是只读的(read-only)。 (您不能以编程方式设置值。)相反,你应该使用 File API 与文件进行交互。

    class FileInput extends React.Component {
      constructor(props) {
        super(props);
        this.handleSubmit = this.handleSubmit.bind(
          this
        );
      }
      handleSubmit(event) {
        event.preventDefault();
        alert(
          `Selected file - ${
            this.fileInput.files[0].name
          }`
        );
      }
    
      render() {
        return (
          <form
            onSubmit={this.handleSubmit}>
            <label>
              Upload file:
              <input
                type="file"
                ref={input => {
                  this.fileInput = input;
                }}
              />
            </label>
            <br />
            <button type="submit">
              Submit
            </button>
          </form>
        );
      }
    }
    
    ReactDOM.render(
      <FileInput />,
      document.getElementById('root')
    );
    

    处理多个输入元素

    • 当您需要处理多个受控的 input 元素时,您可以为每个元素添加一个 name 属性,并且让处理函数根据 event.target.name 的值来选择要做什么。
      forExample:
    class Reservation extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          isGoing: true,
          numberOfGuests: 2
        };
    
        this.handleInputChange = this.handleInputChange.bind(this);
      }
    
      handleInputChange(event) {
        const target = event.target;
        const value = target.type === 'checkbox' ? target.checked : target.value;
        const name = target.name;
    
        this.setState({
          [name]: value
        });
      }
    
      render() {
        return (
          <form>
            <label>
              Is going:
              <input
                name="isGoing"
                type="checkbox"
                checked={this.state.isGoing}
                onChange={this.handleInputChange} />
            </label>
            <br />
            <label>
              Number of guests:
              <input
                name="numberOfGuests"
                type="number"
                value={this.state.numberOfGuests}
                onChange={this.handleInputChange} />
            </label>
          </form>
        );
      }
    }
    

    状态提升(Lifting State Up)

    • 两个组件都将其值保持在本地状态中,但是我们希望弄些状态是共用的,在React中,共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。
    • 我们知道 props(属性) 是只读的。在 React 中,通常通过使组件“受控”的方式来解决。就像 DOM <input>一样接受一个 value 和一个 onChange prop(属性)
      forExample:
    handleChange(e) {
        // 之前是: this.setState({temperature: e.target.value});
        this.props.onTemperatureChange(e.target.value);
    

    onTemperatureChange prop(属性) 和 temperature prop(属性) 一起由父级的 Calculator 组件提供。它将通过修改自己的本地 state(状态) 来处理变更,从而通过新值重新渲染两个输入。我们将很快看到新的 Calculator 实现。

    经验总结

    • 在一个 React 应用中,对于任何可变的数据都应该循序“单一数据源”原则。通常情况下,state 首先被添加到需要它进行渲染的组件。然后,如果其它的组件也需要它,你可以提升状态到它们最近的祖先组件。你应该依赖 从上到下的数据流向 ,而不是试图在不同的组件中同步状态。

    组合 VS 继承

    包含

    • 我们建议这种组件使用特别的 children prop 来直接传递 子元素到他们的输出中(好比vue的slot):
      forExample:
    function FancyBorder(props) {
      return (
        <div className={'FancyBorder FancyBorder-' + props.color}>
          {props.children}
        </div>
      );
    }
    

    这允许其他组件通过嵌套 JSX 传递任意子组件给他们:

    function WelcomeDialog() {
      return (
        <FancyBorder color="blue">
            //***************
          <h1 className="Dialog-title">
            Welcome
          </h1>
          <p className="Dialog-message">
            Thank you for visiting our spacecraft!
          </p>
           //***************
        </FancyBorder>
      );
    }
    
    • 然而这并不常见,有时候,在一个组件中你可能需要多个 “占位符” 。在这种情况下,你可以使用自定义的 prop(属性),而不是使用 children :
    function SplitPane(props) {
      return (
        <div className="SplitPane">
          <div className="SplitPane-left">
            {props.left}
          </div>
          <div className="SplitPane-right">
            {props.right}
          </div>
        </div>
      );
    }
    
    function App() {
      return (
        <SplitPane
          left={
            <Contacts />
          }
          right={
            <Chat />
          } />
      );
    }
    

    React 的编程思想

    • 该如何拆分组件呢?
      其实只需要像拆分一个新方法或新对象一样的方式即可。一个常用的技巧是单一职责原则,即一个组件理想情况下只处理一件事。如果一个组件持续膨胀,就应该将其拆分为多个更小的组件中。

    • 不要重复你自己 (DRY,don’t repeat yourself)

    • 让我们仔细分析每一个数据,弄清楚哪一个是 state(状态) 。请简单地提出有关每个数据的 3 个问题:

    1. 是否通过 props(属性) 从父级传入? 如果是这样,它可能不是 state(状态) 。
    2. 是否永远不会发生变化? 如果是这样,它可能不是 state(状态)。
    3. 是否可以由组件中其他的 state(状态) 或 props(属性) 计算得出?如果是这样,则它不是 state(状态)。
    React 单向数据流在层级中自上而下进行。这样有可能不能立即判断出状态属于哪个组件。这常常是新手最难理解的一部分,试着按下面的步骤分析操作:
    • 确定每个基于这个 state(状态) 渲染的组件。
    • 找出公共父级组件(一个单独的组件,在组件层级中位于所有需要这个 state(状态) 的组件的上面。愚人码头注:父级组件)。
    • 公共父级组件 或者 另一个更高级组件拥有这个 state(状态) 。
    • 如果找不出一个拥有该 state(状态) 的合适组件,可以创建一个简单的新组件来保留这个 state(状态) ,并将其添加到公共父级组件的上层即可。

    相关文章

      网友评论

      本文标题:React文档笔记

      本文链接:https://www.haomeiwen.com/subject/llqjdftx.html