Modularize and abstract react component functionality

馋奶兔 提交于 2020-01-11 07:53:19

问题


I have below a working component that allows for a checkbox all and checkboxes. It works perfectly. However I hate the idea that I'm stuck carrying all of this code around every time I want to use this feature. I'm looking for a way within react to make this modular? Is this

It doesn't modularize the entire functionality of the "input checked all" functionality in one place. I have to move the getInitialState variables and changeHandlers with each usage.

I think of it like if the "input checkbox all" functionally was native within HTML, how would we use it? We would need only supply the attributes to the elements and they would reference each other and all of the handlers would occur under the hood, it would be simple to use. My goal with this example is to have HTML level simplicity. The code I show above doesn't achieve that because it's tied down to function handlers and state initializers. Does react provide a way of abstracting this?

Below is my desired API for this component.

Here's the working example.

The main problems are:

  • The component functionality is indifferent to the parent, meaning the paren't doesn't need to store the information for the handlers and state.
  • The code currently manually tracks state for each checkbox, meaning there's no way to dynamically find how many of an a checkbox is in the DOM without stating it.
  • Overall modularity and ease-of use.

Here's the code:

var InputCheckbox = React.createClass({
  getDefaultProps: function () {
    return {
      checked: false
    }
  },
  render: function () {
    return (
    <input
           checked={this.props.checked}
           type='checkbox'
           {...this.props}/>
    )
  }
})

var Test = React.createClass({
  getInitialState: function () {
    return {
      checked: [false, false, false]
    }
  },
  selectAll: function (event) {
    // Set all checked states to true
    this.setState({
      checked: this.state.checked.map(function () {
        return event.target.checked
      })
    })
  },
  handleChange: function (index, event) {
    var checked = this.state.checked
    checked[index] = event.target.checked
    this.setState({
      checked: checked
    })
  },
  render: function () {
    var isAllChecked = this.state.checked.filter(function (c) {
        return c
      }).length === this.state.checked.length

    return (
    <div>
      Select All:
      <InputCheckbox onChange={this.selectAll} checked={isAllChecked}/>
      <br/>
      <InputCheckbox checked={this.state.checked[0]} onChange={this.handleChange.bind(this, 0)}/>
      <br/>
      <InputCheckbox checked={this.state.checked[1]} onChange={this.handleChange.bind(this, 1)}/>
      <br/>
      <InputCheckbox checked={this.state.checked[2]} onChange={this.handleChange.bind(this, 2)}/>
      <br/>
    </div>
    )
  }
})

React.render(<Test/>, document.body)

Ideally I can just use it like this:

var Checkbox = require('./Checkbox')

var Test = React.createClass({
  render: function () {
    return (
      <div>
        <Checkbox id="alpha"/>
        <Checkbox htmlFor="alpha"/>
        <Checkbox htmlFor="alpha"/>
        <Checkbox htmlFor="alpha"/>
      </div>
    )
  }
})

回答1:


The following example is in the right general direction I think, the general idea is to introduce a wrapper component for the related boxes, and then walk through the children in that component to tie them together.

var CheckAll = React.createClass({
render() {
  return <input type="checkbox" {...this.props} />
}
});
var Checkbox = React.createClass({
render() {
  return <input type="checkbox" {...this.props} />
}
});
var CheckboxGroup = React.createClass({
setAll(to) {
  var result = {};
  Object.keys(this.props.boxes).forEach(k => result[k] = to)
  this.props.onChange(result);
},
setOne(name, to) {
  var result = {};
  Object.keys(this.props.boxes).forEach(k => result[k] = this.props.boxes[k])
  result[name] = to;
  this.props.onChange(result);
},
enrichChild(child) {
  var boxes = this.props.boxes;
  var all = Object.keys(boxes).every(k => boxes[k]);
  if (child.type == CheckAll) {
    return React.cloneElement(child, { checked: all,
      onChange: () => this.setAll(!all)
    });
  } else if (child.type == Checkbox) {
    var name = child.props.name;
    return React.cloneElement(child, { checked: !!boxes[name],
      onChange: ({target}) => this.setOne(name, target.checked)
    });
  } else {
    return child;
  }
},
render() {
  return (
    <div>
      {React.Children.map(this.props.children, this.enrichChild)}
    </div>
  )
}
});


var Test = React.createClass({
  getInitialState: function () {
    return {
      boxes: {
        a: true,
        b: false,
        c: false,
      }
    }
  },
  render: function () {
    return (
    <div>
      <CheckboxGroup
        boxes={this.state.boxes}
        onChange={boxes => this.setState({boxes})}
      >
        <CheckAll />
        <Checkbox name="a" />
        <Checkbox name="b" />
        <Checkbox name="c" />
      </CheckboxGroup>
    </div>
    )
  }
})

React.render(<Test/>, document.body)

Here's a jsbin - https://jsbin.com/zomuxolevo/1/edit?js,output

To allow for more flexibility with the children, you'd need to recursively walk them using something like this gist https://gist.github.com/dandelany/1ff06f4fa1f8d6f89c5e

var RecursiveChildComponent = React.createClass({
  render() {
    return <div>
      {this.recursiveCloneChildren(this.props.children)}
    </div>
  },
  recursiveCloneChildren(children) {
    return React.Children.map(children, child => {
      if(!_.isObject(child)) return child;
      var childProps = {someNew: "propToAdd"};
      childProps.children = this.recursiveCloneChildren(child.props.children);
      return React.cloneElement(child, childProps);
    })
  }
})



回答2:


I hacked this together using some jQuery, and lodash.

Here's the example running.

Note, this example goes into the DOM to get the data needed. None of the state of these checkboxes are stored by the component. As far as I can tell there is no true "React" way to do this. (I am very open to suggestion.)

var Checkbox = React.createClass({
  componentDidMount: function () {
    var component = React.findDOMNode(this)
    var $component = $(component)
    if ($component.attr('id')) {
      var selector = 'input'
      selector += '[data-component=Checkbox]'
      selector += '[for=' + $component.attr('id') + ']'
      var $forComponents = $(selector)
      $component.on('change', function () {
        var value = $component.prop('checked')
        $forComponents.each(function () {
          $forComponent = $(this)
          $forComponent.prop('checked', value)
        })
      })
      $forComponents.on('change', function () {
        var values = $forComponents.map(function () {
          var $forComponent = $(this)
          var value = $forComponent.prop('checked')
          return value
        })
        var simple = _.chain(values).unique().value()
        if (simple.length === 1 && simple[0] === true) {
          $component.prop('checked', true)
        } else {
          $component.prop('checked', false)
        }
      })
    }
  },
  render: function () {
    return (
    <input
       type='checkbox'
       data-component='Checkbox'
       {...this.props}/>
    )
  }
})

var Test = React.createClass({
  render: function () {
    return (
    <div>
      Select All: <Checkbox id='alpha'/><br/>
      <Checkbox htmlFor='alpha'/><br/>
      <Checkbox htmlFor='alpha'/><br/>
      <Checkbox htmlFor='alpha' defaultChecked/><br/>
      <Checkbox htmlFor='alpha'/><br/>
      <Checkbox htmlFor='alpha'/><br/>
    </div>
    )
  }
})

React.render(<Test/>, document.body)


来源:https://stackoverflow.com/questions/32644711/modularize-and-abstract-react-component-functionality

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!