React中需要注意的地方(二)

本文深入探讨了React开发中常见的反面模式,包括在getInitialState中使用props、直接操作DOM事件、使用ajax加载初始数据等,并提供了改进建议。文章还强调了组件间通信的有效方式,以及如何正确地使用组件引用和暴露功能。通过遵循最佳实践,开发者可以提升应用程序的性能和维护性。

一、在getInitialState里边使用 props 是一种反面模式

提示:这不是React明确规定的,就像经常发生在其它代码中的那样;在这种情况下,React能使这种模式更清晰。

    在getInitialState中使用props传递的父组件的数据来绑定state,经常会导致所谓的真理的重复来源(就是说可能分不清数据来源了)。只要有可能动态推断值,以确定不会发生不同步以及维护困难的情况。(小僧翻译出来也快吐了,原文如果不引入俚语还好,一旦用了就晕乎了,不过在最下方小僧会大致总结下,客官勿急)。

    反面示例:

var MessageBox = React.createClass({
  getInitialState: function() {
    return {nameWithQualifier: 'Mr. ' + this.props.name};
  },

  render: function() {
    return <div>{this.state.nameWithQualifier}</div>;
  }
});

React.render(<MessageBox name="Rogers"/>, mountNode);

    好的示例:

var MessageBox = React.createClass({
  render: function() {
    return <div>{'Mr. ' + this.props.name}</div>;
  }
});

React.render(<MessageBox name="Rogers"/>, mountNode);

 

    然而,如果你明确表示同步不是你的目标,那么也可以写成非反向模式。

var Counter = React.createClass({
  getInitialState: function() {
    // naming it initialX clearly indicates that the only purpose
    // of the passed down prop is to initialize something internally
    return {count: this.props.initialCount};
  },

  handleClick: function() {
    this.setState({count: this.state.count + 1});
  },

  render: function() {
    return <div onClick={this.handleClick}>{this.state.count}</div>;
  }
});

React.render(<Counter initialCount={7}/>, mountNode);

    个人总结:getInitialState中初始化的数据,原则是保持在该组件及其子组件使用,不要将父组件的数据(props传递过来的数据)使用在这里。如果你想在该组件内使用的数据需要根据props传递过来的数据来初始化,那就是你的数据原型有问题了,为什么不在其它方法中直接使用父组件的数据。与父组件通信,最好是通过回调函数,该回调函数是从父组件传递过来,并且里边封装了state绑定的数据(即更改的数据)。


二、在组件中使用DOM事件

注意:这里说的事件是非React提供的事件。React有关于DOM的操作可以很容易和其它类库合作,如jQuery。

    试着改变窗口大小:

var Box = React.createClass({
  getInitialState: function() {
    return {windowWidth: window.innerWidth};
  },

  handleResize: function(e) {
    this.setState({windowWidth: window.innerWidth});
  },

  componentDidMount: function() {
    window.addEventListener('resize', this.handleResize);
  },

  componentWillUnmount: function() {
    window.removeEventListener('resize', this.handleResize);
  },

  render: function() {
    return <div>Current window width: {this.state.windowWidth}</div>;
  }
});

React.render(<Box />, mountNode);
   componentDidMount 会在组件被渲染之后被调用。在这个方法中通常被用来给DOM添加事件,因为只有DOM(渲染成功)存在,才能给其添加事件。

      注意这里的事件是绑定到了React中的组件,而不是最原始的DOM。React会通过一个叫 autobinding 的过程将这个事件方法绑定到组件中。


三、用ajax加载初始数据

    在componentDidMount中获取数据:当响应到达时,会将数据存储的state中,然后触发更新UI。

    当处理异步请求的响应时,要确定当前组件是否仍然在更新中,通过 this.isMounted() 来判断。

下边的例子是获取所需的Github用户最新动态:

 

var UserGist = React.createClass({
  getInitialState: function() {
    return {
      username: '',
      lastGistUrl: ''
    };
  },

  componentDidMount: function() {
    $.get(this.props.source, function(result) {
      var lastGist = result[0];
      if (this.isMounted()) {
        this.setState({
          username: lastGist.owner.login,
          lastGistUrl: lastGist.html_url
        });
      }
    }.bind(this));
  },<pre name="code" class="javascript">React.render(<div id={false} />, mountNode);

render: function() { return ( <div> {this.state.username}'s last gist is <a href={this.state.lastGistUrl}>here</a>. </div> ); }});React.render( <UserGist source="https://api.github.com/users/octocat/gists" />, mountNode);


注意:例子中的bind(this),只能用在函数

四、在JSX中使用false

    下边是不同情况系 false 渲染的过程:

    1. 渲染成id=“false”:

React.render(<div id={false} />, mountNode);
    2. 渲染成输入框中的字符串值
React.render(<input value={false} />, mountNode);
    3. 没有子元素的情况
React.render(<div>{false}</div>, mountNode);
在这种情况中,false被渲染成<div></div>之间的文本,而是作为一个div的子元素,是为了可以有更多灵活的用法:如
<div>{x > 1 && 'You have more than one item'}</div>

五、组件间的通信

    在父子组件之间的通信,可以简单的通过props。

    而在同级子组件之间的通信:假如你的 GroceryList 组件有很多通过数组生成的条目。当其中一个条目被点击时,你想要展示该条目的名称。

var GroceryList = React.createClass({
  handleClick: function(i) {
    console.log('You clicked: ' + this.props.items[i]);
  },

  render: function() {
    return (
      <div>
        {this.props.items.map(function(item, i) {
          return (
            <div onClick={this.handleClick.bind(this, i)} key={i}>{item}</div>
          );
        }, this)}
      </div>
    );
  }
});

React.render(
  <GroceryList items={['Apple', 'Banana', 'Cranberry']} />, mountNode
);
注意bind(this, arg1, arg2, ...)的用法:这里我们只是去传递更多的参数给 handleClick 方法。这并不是React新加的规定,而是JavaScript就有的。

    在两个同级子组件之间的通信,你可以设置一个全局的事件componentWillUnmount。在 componentDidMount 中注册监听,在componentWillUnmount 卸载监听,当监听到一个事件时,调用setState()更新视图。


六、暴露组件的功能

    组件之间的通信也有一个不常见的方法:可以简单的在子组件中调用父组件的回调函数,此处的子组件拆分更加琐碎。

    下边的例子中,每点击一个条目然后自身被删除,当仅剩一个条目时,就为其加上动画效果。

var Todo = React.createClass({
  render: function() {
    return <div onClick={this.props.onClick}>{this.props.title}</div>;
  },

  //this component will be accessed by the parent through the `ref` attribute
  animate: function() {
    console.log('Pretend %s is animating', this.props.title);
  }
});

var Todos = React.createClass({
  getInitialState: function() {
    return {items: ['Apple', 'Banana', 'Cranberry']};
  },

  handleClick: function(index) {
    var items = this.state.items.filter(function(item, i) {
      return index !== i;
    });
    this.setState({items: items}, function() {
      if (items.length === 1) {
        this.refs.item0.animate();
      }
    }.bind(this));
  },

  render: function() {
    return (
      <div>
        {this.state.items.map(function(item, i) {
          var boundClick = this.handleClick.bind(this, i);
          return (
            <Todo onClick={boundClick} key={i} title={item} ref={'item' + i} />
          );
        }, this)}
      </div>
    );
  }
});

React.render(<Todos />, mountNode);

    或者,你也可以通过在子组件 todo 中定义一个 isLastUnfinishedItem 属性,然后在 componentDidUpdate 进行判断,是否添加动画效果。然而,当有多个属性来控制动画时就显得混乱了。

七、组件引用

    当你在一个大的非React应用中想要使用React的组件或者将你的代码过渡到React,你需要想办法引用组件。React.render 方法返回的就是一个完整组件的引用。

var myComponent = React.render(<MyComponent />, myContainer);

    要记住,JSX语法并不会返回一个组件实例。它只是一个React元素:一个组件的轻量级表现方式。

var myComponentElement = <MyComponent />; // This is just a ReactElement.

// Some code here...

var myComponentInstance = React.render(myComponentElement, myContainer);
  注意:这里只能在顶级作用域(组件外部)中使用。在组件内部应该使用 props 和 state 来保持组件间的通信。

   

八、this.props.children 未定义

     你不能使用this.props.children来访问组件的子节点。this.props.children被设计用来表示自身的节点。

var App = React.createClass({
  componentDidMount: function() {
    // This doesn't refer to the `span`s! It refers to the children between
    // last line's `<App></App>`, which are undefined.
    console.log(this.props.children);
  },

  render: function() {
    return <div><span/><span/></div>;
  }
});

React.render(<App></App>, mountNode);
  想要访问子节点,就要使用在span中ref


### React.js 中 `React.ReactNode` 的定义与使用场景 #### 什么是 `React.ReactNode` 在 React.js 中,`React.ReactNode` 是 TypeScript 类型系统中的一个类型别名。它表示可以作为子节点传递给 JSX 元素的内容[^5]。具体来说,它可以是一个字符串、数字、布尔值、数组、React 组件实例或者 `null`/`undefined`。 以下是 `React.ReactNode` 可能的取值范围: - **字符串**: 如 `"Hello, world!"`。 - **数字**: 如 `42` 或者 `-7.89`。 - **布尔值**: 虽然可以传入 `true` 或 `false`,但在渲染时会被忽略。 - **数组**: 包含多个子节点的集合,例如 `[<span>Item</span>, <p>Hello!</p>]`。 - **React 元素**: 使用 `React.createElement` 或 JSX 创建的组件实例。 - **Fragment**: 特殊类型的容器 `<></>`,用于包裹一组兄弟节点而不引入额外 DOM 结构。 - **Portals**: 将子节点渲染到不同的 DOM 子树中的一种方式。 - **Null / Undefined**: 表示无内容。 这种灵活性使得 `React.ReactNode` 成为一种通用的类型,在许多地方都可以看到它的身影。 --- #### `React.ReactNode` 的典型使用场景 1. **定义组件的 Props** 当希望某个 Prop 接收任意合法的 React 子节点时,通常会将其类型设置为 `React.ReactNode`。这适用于需要动态插入内容的情况。 ```tsx interface MyComponentProps { children: React.ReactNode; } const MyComponent: React.FC<MyComponentProps> = ({ children }) => ( <div> <h1>Title</h1> {children} </div> ); // 使用示例 <MyComponent> <p>This is a paragraph.</p> </MyComponent>; ``` 2. **条件渲染** 在某些情况下,可能需要根据逻辑判断决定是否显示特定内容。此时可以用 `React.ReactNode` 来统一处理不同类型的返回值。 ```tsx function ConditionalContent({ show }: { show: boolean }): React.ReactNode { return show ? <p>Visible Content</p> : null; } ``` 3. **高阶组件 (HOC)** 高阶组件可能会接受其他组件或纯 HTML 内容作为参数,这时也可以利用 `React.ReactNode` 提供更大的兼容性。 ```tsx function withWrapper(WrappedComponent: React.ComponentType<any>) { return function WrapperComponent(props): React.ReactElement | null { if (!props.shouldRender) return null; return ( <div className="wrapper"> <WrappedComponent {...props} /> </div> ); }; } ``` 4. **组合 UI 块** 如果正在设计可重用的小部件库,则允许这些小部件支持多种输入形式是非常重要的。通过指定 `React.ReactNode`,可以让 API 更加灵活友好。 ```tsx type ButtonVariant = "primary" | "secondary"; interface ButtonProps { label?: string; // 文本标签 icon?: React.ReactNode; // 自定义图标或其他装饰物 variant?: ButtonVariant; } const CustomButton: React.FC<ButtonProps> = ({ label, icon, variant = "primary", }) => ( <button className={`btn-${variant}`}> {icon && <span>{icon}</span>} {label || "Default Label"} </button> ); ``` --- #### 注意事项 尽管 `React.ReactNode` 很强大,但也需要注意以下几点: - 不要滥用泛型化。如果能够更精确地限定某一部分的具体结构(比如只接收字符串),那么应该优先考虑这样做以提高代码的安全性和可读性。 - 对于复杂的嵌套情况,务必验证最终生成的实际 DOM 是否符合预期效果。 - 记住即使指定了 `React.ReactNode`,仍然无法阻止运行时期错误的发生——因此始终建议配合单元测试一起工作。 --- ### 示例代码 下面是一段完整的例子展示了如何运用 `React.ReactNode` 构建一个简单的布局工具箱。 ```tsx import React from 'react'; interface BoxProps { title: string; content: React.ReactNode; // 支持任何有效的子节点 } const Box: React.FC<BoxProps> = ({ title, content }) => ( <section style={{ border: '1px solid gray', padding: '1em' }}> <header><strong>{title}</strong></header> <article>{content}</article> </section> ); // 渲染不同类型的内容 function App(): JSX.Element { return ( <> {/* 字符串 */} <Box title="Text Example" content="Just plain text." /> {/* 数组 */} <Box title="List of Items" content={[ <li key={0}>First item</li>, <li key={1}>Second item</li>, ]} /> {/* 复合组件 */} <Box title="Complex Component" content={<CustomWidget />} /> </> ); } const CustomWidget = () => <div>A custom widget here...</div>; export default App; ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值