React查缺补漏之二

译文链接

  1. 通过给一个通用函数传入参数定制特定函数的用法

    _onFieldChange函数是一个通用实例方法,通过给这个函数传入不同的参数来实现返回结果的不同。

    在构造函数中,进行绑定(没有想过这种用法)。

  1. `this._onNameChanged = this._onFieldChange.bind(this, 'name');`
  2. `this._onPasswordChanged =this._onFieldChange.bind(this, 'password');`

**注意点击的回调事件要写在事件处理函数参数的最后**,上述在绑定的过程中传入的参数相当于第一个参数。

这是因为:

1. 使用箭头函数,事件对象e要显式传递;

2. 使用bind方法,事件对象e会隐式传递,e要放在参数的最后。

这里使用的是bind方法,事件对象会隐士传递,所以可以直接在`input`的`onChange`属性中单纯传入回调函数并且回调事件要写在回调函数参数的最后。
12345678910111213141516171819
class Form extends React.Component {  constructor(props) {    super(props);    this._onNameChanged = this._onFieldChange.bind(this, 'name');    this._onPasswordChanged =      this._onFieldChange.bind(this, 'password');  }  render() {    return (      <form>        <input onChange={ this._onNameChanged } />        <input onChange={ this._onPasswordChanged } />      </form>    );  }  _onFieldChange(field, event) {    console.log(`${ field } changed to ${ event.target.value }`);  }};

  1. 解绑强耦合组件的讨论——组合

    作者认为组合是React的最强大的优势之一。

    书中的组合部分涉及组件作为属性传递高阶组件,自己现在在写代码的时候会有意的将每个组件都写成单一功能的组件,自己感觉主要的好处还是视觉上的,不会出现看起来庞大数量巨大到恐怖的组件,改bug比较好定位。现在写的多数都是一次性组件,也没有写过组件测试,所以代码重用的优点感受的不明显,降低测试难度完全没有体会。

    1. children属性

      这个属性之前没有好好关注过,如今看来是个相当好用的属性。书里说这个属性一大用途是用来解绑父组件和子组件的强耦合。

    2. 将组件作为参数传入另一个组件

      其实上面的children属性也相当与将组件作为参数传入另一个组件。之所以现在不太用这种写法是因为自己还没有写过通用组件,比如项目里的各种modal。写的全都是一次性的组件,写成强耦合的组件或许更直观,代码量也能减少,但如果涉及到组件测试就完逼了,最近写的组件业务逻辑都比较复杂了,组件的测试也肯定会相当复杂。(之前自己从没写过测试组件,现在要练习下。)

    3. 高阶组件

      高阶组件的调用要在render()之前,生成一个唯一的组件,如果在render()中调用,每次生成的都不一样,会产生性能问题,见作者观点

      下面是一个典型的高阶组件,在组件强化函数中,Component组件的参数title和remoteTitle的数据来源一个是配置文件,一个是ajax请求,即能控制原始组建的输入,同时还能传入高阶组件的props,书里说这是高阶组件的最大优点(控制数据来源)。感觉甚至可以把ajax请求作为高阶组件的参数传入,这样即保证组件的功能单一可复用,又能让数据和对应的组件组合在一起,还能避免原先的在上层的父组件中写入和父组件完全不相关的数据请求函数。

      12345678910111213141516171819202122232425262728
        var config = require('path/to/configuration');
      
        var enhanceComponent = (Component) =>  class Enhance extends React.Component {    constructor(props) {      super(props);
      
            this.state = { remoteTitle: null };    }    componentDidMount() {      fetchRemoteData('path/to/endpoint').then(data => {        this.setState({ remoteTitle: data.title });      });    }    render() {      return (        <Component          {...this.props}          title={ config.appTitle }          remoteTitle={ this.state.remoteTitle }        />      )    }  };
      
      var OriginalTitle  = ({ title, remoteTitle }) =>  <h1>{ title }{ remoteTitle }</h1>;var EnhancedTitle = enhanceComponent(OriginalTitle);
4. 将函数作为children和render props

    下面这个例子里,App组件中不仅有全部数据,而且还有如何显示数据的方法。TodoList组件单纯只是一个显示数据的容器。使用时,将数据和操作显示数据的方法传入TodoList组件。感觉这个模式和自己之前的想法不太一样。之前自己写代码只是考虑组件功能的单一性,单从这个角度我肯定会把显示数据的方法写在TodoList组件里面,这样做完全没有考虑TodoList组件的复用,显然也没有让TodoList组件成为纯粹的样式渲染组件,明显`将函数作为children`模式以及`render props`模式更胜一筹。
1234567891011121314151617181920212223242526272829
function TodoList({ todos, children }) {  return (    <section className='main-section'>      <ul className='todo-list'>{        todos.map((todo, i) => (          <li key={ i }>{ children(todo) }</li>        ))      }</ul>    </section>  );}

function App() {  const todos = [    { label: 'Write tests', status: 'done' },    { label: 'Sent report', status: 'progress' },    { label: 'Answer emails', status: 'done' }  ];  const isCompleted = todo => todo.status === 'done';  return (    <TodoList todos={ todos }>      {        todo => isCompleted(todo) ?          <b>{ todo.label }</b> :          todo.label      }    </TodoList>  );}

这里作者举了他们项目的一个例子:

>最近,我们在工作中使用这种模式,我们必须将某些界面限制只对具有 read:products 权限的用户开放。我们使用的是 render prop 模式。

123
<AuthorizepermissionsInclude={[ 'read:products' ]}render={ () => <ProductsList /> } />

这样写组件语义真的好清晰啊,上面的这三行代码不言自明:`Authorize`组件**验证**是否是有(`permissionsInclude`)权限(`[ 'read:products' ]`),如果有,则渲染(`render`)产品列表(`<ProductsList />`)。**这个组件仅靠组件名称、属性名称就完整描述了这个组件的功能,数据和渲染规则来自父组件,具体渲染细节则被封装进单纯的显示组件`Authorize`中。**

哇擦!有些激动!这真的变成代码写的文章了,而不是冗繁的字码,tsubarashi,真的是代码的艺术啊!

**所以今后写组件,不要盲目的用单一性拆分代码,从语义的角度考虑明显更优雅。**

  1. 展示型组件vs容器型组件

    以现在写的项目为例,配置选型的创建功能使用了三个组件:base,first_step,second_step。其中后两个组件为函数组件,遵循了展示型组件的原则,最大限度的不去管理数据。base组件则为一个混合组件,有几乎全部的业务逻辑,还有一部分组合first_stepsecond_step两个组件的显示代码,这一部分可以拆出来作为一个独立的组件,这样就会变成一个纯粹的容器型组件。

  2. 在组件中更新组件外部数据

    下面的代码中,Store.set.bind(Store)中的bind(Store)是关键。将函数的执行环境和Store对象绑定。

    12345678910111213
    var Store = {  _flag: false,  set: function(value) {    this._flag = value;  },  get: function() {    return this._flag;  }};
    
    function App() {  return <Switcher onChange={ Store.set.bind(Store) } />;};
  3. 双向绑定

    之前写的代码里,有几处用到了下面这种写法:

    父组件向子组件传递了一个能setState的函数,当子组件满足某个要求时执行这个函数,修改了父组件中的数据,同时子组件的数据完全来源于父组件,当数据有变化时,子组件再次渲染。

    但上述写法感觉不是双向绑定,双向绑定是相同数据状态存在多处,此刻再进行类似上述操作,数据状态彼此影响,感觉这个是双向绑定。

  4. 单项数据流

    将数据状态存储在全局的一个store对象中,这里作为整个应用数据属性更新的唯一地方。感觉这个是理解Redux的关键啊。下面是书中的例子:

    12345678910111213141516171819202122232425262728293031323334353637383940414243444546
    //数据状态存储和更新的唯一地点
    
    var Store = {  _handlers: [],  _flag: '',  subscribe: function(handler) {    this._handlers.push(handler);  },  set: function(value) {    this._flag = value;    this._handlers.forEach(handler => handler(value))  },  get: function() {    return this._flag;  }};
    
    //状态组件的数据来源依然是store//这个组件初始化的时候注册了一个本组件修改状态的函数//然后将最终修改函数Store.set传给子组件,当子组件满足条件时发动
    
    class App extends React.Component {  constructor(props) {    super(props);
    
        this.state = { value: Store.get() };    Store.subscribe(value => this.setState({ value }));  }  render() {    return (      <div>        <Switcher          value={ this.state.value }          onChange={ Store.set.bind(Store) } />      </div>    );  }};
    
    function Switcher({ value, onChange }) {  return (    <button onClick={ e => onChange(!value) }>      { value ? 'lights on' : 'lights off' }    </button>  );};
  5. 感觉这本书里讲redux虽然没有很深入但是很好理解,译文点击此处
  6. React context

    之前没有用过这个功能,只在redux中有见过Provider,但这个组件来自react-redux

    下面这段是文章中对context功能的使用说明:

    createContext 返回的对象具有 Provider 和 Consumer 属性。它们实际上是有效的 React 类。Provider 以 value 属性的形式接收 context 。Consumer 用来访问 context 并从中读取数据。

    123456
    import { createContext } from 'react';
    
    const Context = createContext({});
    
    export const Provider = Context.Provider;export const Consumer = Context.Consumer;

    使用:

    1. 传入context

      12345678910111213
      import { Provider } from './context';
      
      const context = { title: 'React In Patterns' };
      
      class App extends React.Component {  render() {    return (      <Provider value={ context }>        <Header />      </Provider>    );  }};
    2. 在子组件中使用Comsumer

      Consumer 类使用函数作为嵌套子元素 ( render prop 模式) 来传递 context 。

      123456789
      import { Consumer } from './context';
      
      function Title() {  return (    <Consumer>{      ({ title }) => <h1>Title: { title }</h1>    }</Consumer>  );}
  7. 使用模块系统

    技术:模块创建全局对象+高阶组件

    描述:之前的项目中,创建数据缓存和router都使用了类似的技术。

    方法:在创建三个函数和一个存储对象

    1. 存储对象

      1
      var dependencies = {};
    2. 注册函数用来获取属性
      12
      export function register(key, dependency) {dependencies[key] = dependency;
    3. 获取函数用来拿到需要的值
      123
      export function fetch(key) {if (dependencies[key]) return dependencies[key];throw new Error(`${ key } is not registered as dependency.`);
    4. 高阶函数用来包装原始组件,获取属性
      1234567891011121314151617
      export function wire(Component, deps, mapper) {  return class Injector extends React.Component {    constructor(props) {      super(props);      this._resolvedDependencies = mapper(...deps.map(fetch));    }    render() {      return (        <Component          {...this.state}//这里表示的是Injector这个组件维护的状态,但感觉不太有用的到的机会,这种通用函数因该很少有这样的状态吧          {...this.props}//父组件传入的属性          {...this._resolvedDependencies}        />      );    }  };}

原文:大专栏  React查缺补漏之二

原文地址:https://www.cnblogs.com/sanxiandoupi/p/11631622.html

时间: 2024-08-27 20:09:46

React查缺补漏之二的相关文章

回顾CSS,查缺补漏(二)

属性选择符,根据部分属性值选择 使用属性选择符匹配子串 [foo|="bar"] 选择的元素有foo属性,且其值以bar和一个英文破折号(U+002D)开头,或者本身就是bar本身 [foo|="bar"]选择的元素有foo属性,且其值是包含bar这个词的一组词 [foo|="bar"]选择的元素有foo属性,且其值包含子串bar [foo|="bar"]选择的元素有foo属性,且其值以bar开头 [foo|="ba

Android查缺补漏--Service和IntentService

Service的运行不依赖界面,即使程序被切换到后台,Service仍然能够保持正常运行.当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行. Service 分为启动状态和绑定状态.当处于仅启动状态时,通过 stopService或 stopSelf 即可停止 Service.当处于绑定状态时需要通过 unBindService 和 stopService 结合使用才能完全停止 Service. 一.Service的生命周期(onCreate()-onStartComma

Android查缺补漏--ContentProvider的使用

ContentProvider (内容提供者)是一种共享型组件,可以为系统内应用于与应用之间提供访问接口. ContentProvide要想正常工作需要三个关键点: ContentProvider:对外提供数据的访问接口. Uri:ContentProvider的唯一标识,外界可根据其访问对应的ContentProvider. ContentResolver 比如,当应用A想把自己数据暴露出来让别的应用也可以操作的话,就可以在应用A内部创建一个ContentProvider实现相关方法并添加UR

Android查缺补漏(View篇)--在 Activity 的 onCreate() 方法中为什么获取 View 的宽和高为0?

在 Activity 的 onCreate() 方法中为什么获取 View 的宽和高为0 ? @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my_view); myview = ViewUtils.find(this, R.id.myview); getViewSize("onCr

Android查缺补漏(线程篇)-- IntentService的源码浅析

本文作者:CodingBlock 文章链接:http://www.cnblogs.com/codingblock/p/8975114.html 在Android中有两个比较容易弄混的概念,Service和Thread,我们都知道这两个东西都可以执行后台任务,但要注意的是Service是跑在主线程中的,如果不做特殊处理是会阻塞主线程的,而IntentService正好弥补了这一点,在<Android查缺补漏--Service和IntentService>这篇博文中已经简单介绍过了IntentSe

Android查缺补漏(View篇)--自定义 View 的基本流程

View是Android很重要的一部分,常用的View有Button.TextView.EditView.ListView.GridView.各种layout等等,开发者通过对这些View的各种组合以形成丰富多彩的交互界面,一个应用中界面交互的体验往往在应用的受欢迎程度上起了很关键得作用,所以开发者们大多会想方设法的做出一个更加精美的界面,例如:通过自定义View.深入学习View的原理以便更好的对其优化使其在操作起来更加流畅等等,也正因为如此,在面试中View也常常作为面试官重点考察的对象之一

Android查缺补漏(View篇)--自定义View利器Canvas和Paint详解

上篇文章介绍了自定义View的创建流程,从宏观上给出了一个自定义View的创建步骤,本篇是上一篇文章的延续,介绍了自定义View中两个必不可少的工具Canvas和Paint,从细节上更进一步的讲解自定义View的详细绘制方法.如果把自定义View比作盖一座房子,那么上篇文章就相当于教会了我们怎么一步步的搭建房子的骨架,而本篇文章将要教会我们的是为房子的骨架添砖加瓦直至成型,甚至是怎么装修. Canvas 为了后文更为方便的讲解Canvas的常用方法的使用,我们先来做一些准备工作,创建一个自定义V

Android查缺补漏(View篇)--事件分发机制源码分析

在上一篇博文中分析了事件分发的流程及规则,本篇会从源码的角度更进一步理解事件分发机制的原理,如果对事件分发规则还不太清楚的童鞋,建议先看一下上一篇博文 <Android查缺补漏(View篇)--事件分发机制> ,先来看一下本篇的分析思路,一会儿会按照事件传递的顺序,针对以下几点进行源码分析: Activity对点击事件的分发过程 PhoneWindow是如何处理点击事件的 顶级View对点击事件的分发过程 View对点击事件的处理过程 Activity对点击事件的分发过程 通过上一篇博文中我们

20165306 第二周查缺补漏

第二周查缺补漏 1.位运算符 位运算符主要针对两个二进制数的位进行逻辑运算,所以要先把其他进制数转化为二进制数. 例如: 128&129 128转换成二进制就是10000000,129转换成二进制就是10000001,根据与运算符的运算规律,"只有两个位都是1,结果才是1",可以知道结果就是10000000,即128. 128|129 根据或运算符的运算规律,"只要两个位有一个是1,结果就是1",可以知道结果就是10000001,即129. 15^2 15转