这一章, 我们来看看如何在 pastate 里渲染和操作表单元素.
使用原生表单元素
我们在 BasicInfoView 组件的两个按钮下面添加一个输入框用于输入姓名, 并添加一个勾选框用于选择性别, 更改如下:
- class BasicInfoView extends PureComponent {render() {
- ...
- return (
- <div style={{ padding: 10, margin: 10 }}>
- ...
- <div>
- <button onClick={this.decreaseAge}> decrease age </button>
- <button onClick={this.increaseAge}> increase age </button>
- </div>
- <div>
- name: <input type="text" value={state.name} onChange={this.handleNameChange}/> <br />
- Is boy: <input type="checkbox" checked={state.isBoy == true} onChange={this.handleIsBoyChange}/>
- </div>
- </div>
- )
- }
- }
上面添加两个了 input 标签, 第一个 input 使用 name 数据, 第二个 input 使用 isBoy 数据. 同时我们也先指定两个 input 的 onChange 处理函数.
注意: 如前面章节所提及, 对于 imState 布尔值, 请记得使用显式布尔值方式:
- checked={state.isBoy == true}
- .
接下来看看如何实现两个 onChange 处理函数:
- class BasicInfoView extends PureComponent {
- ...
- handleNameChange(e){
- state.basicInfo.name = e.target.value
- store.sync() // 编辑中的输入框, 需手动同步 store
- }
- handleIsBoyChange(e){
- state.basicInfo.isBoy = e.target.checked
- }
- ...
- }
非常熟悉和简单! 你只需把更新的值赋给目标 state 节点即可. 我们会发现多了个 store.sync(), 这个函数是让 pastate 立刻执行数据同步更新. 由于很多输入法会在输入过程中会把带下划线的 "拼音字母" 输入到 input 中, 如下:
把拼音值输入文本框的输入法
如果 pastate 执行异步更新会使带下划线 "拼音字母" 中断, 因此我们在更新 编辑中的输入框 时, 需要简单地使用 store.sync() 执行同步更新, 让 "拼音" 连续. 如果不是对 编辑中的输入框 进行修改或不需要支持输入法输入(如密码等), 无需使用 store.sync() .
初步完成! 我们试着往输入框输入文字, 或点击勾选框, 可以看到文字区域的值与 input 区域的视图都正常地进行更新!
与普通 react 表单的处理模式一样, 我们可以通过 onChange 函数的实时控制输入的内容, 如转化大小写, 控制字符串长度等:
- class BasicInfoView extends PureComponent {
- ...
- // 把输入值都转化为大写
- handleNameChange_uppercase(e){
- state.basicInfo.name = e.target.value.toUpperCase()
- store.sync()
- }
- // 控制文本长度在 10 个字符以内
- handleNameChange_limitedLength(e){
- if(e.target.value.length> 10) return;
- state.basicInfo.name = e.target.value
- store.sync()
- }
- ...
- }
使用 pastate 双向绑定输入组件
使用过 vue.js 或 angular.js 的人都体验过自动双向数据绑定 (two-ways data binding) 的便捷性, 但由于 react state 渲染的单向数据流原则, 在 react 中没有默认提供这个功能. 而 pastate 的 imState 具有保存节点信息的特殊性, 能够很容易实现自动双向数据绑定功能!
pastate 为你提供了四个已实现双向数据绑定的高阶表单组件 (Higher Order Component, 通常简称 HOC) , 这些组件都是基于 PureComponent 实现的, 具有良好的渲染性能, 你可以尽情享用他们! 这四个表单组件如下:
Input : 文本框
Checkbox : 复选框
RadioGroup : 单选框选项组
Select : 下拉选择框
Input 文本框
Input 组件可以用于显示单行输入框和多行输入框, 只需要传入要绑定的值, 当你通过界面更改输入框的值时, Input 组件会自动为你更新所绑定的 state :
- import {...,
- Input
- }
- from 'pastate'...render() {
- let state = this.props.state
- return (... <Input value = {
- state.text1
- }
- /> {/* * 单行输入框, 内部使用 <input type = "text" />实现 */}
- <Input value={state.text2} type="textarea" /> {
- /** 多行输入框, 内部使用 <textarea /> 实现 */
- }...)
- }...
Input 组件的属性及其说明如下:
属性 | 值 | 说明 |
---|---|---|
value | string | number , 必填 | 绑定的值,需要使用 this.props.state 中的节点 , 即 imState |
type | "text" | "textarea" | "password" | "number" | 输入框类型,默认为 "text" |
beforeChange | (newValue: string | number, oldValue: string | number) => string | number | 在绑定值更新前会被调用,可用于实现自定义字符串更新逻辑, 如控制大小写或限制字符串长度等;返回值为最终要更新的值 |
afterChange | (newValue: string | number) => void | 在绑定值更新后会被调用 |
disabled | boolean | 指定输入框是否处于禁止状态,默认为 false |
className | string | 传递给输入框的 class 名 ( 用于指定 CSS 样式等 ) |
id | string | 传递给输入框的 id 名 ( 用于指定 css 样式等 ) |
useComposedValue | boolean | [ 实验特性 ] 指定是否在输入法完成拼音过程时才更新 state 的模式,开启后,在输入拼音的过程中绑定的值不会更新,默认为 false |
Input 组件实现了自动绑定数据双向绑定功能, 如果你需要自定义字符串更新逻辑, 或者在字符值更新后做一些操作, 可以通过指定可选的 beforeChange 函数和 afterChange 函数来实现.
- ...
- handleTextBeforeChange(newValue, oldValue) {
- // 把输入的字符转化为大写
- return newValue.toUpperCase()
- }
- render(){
- let state = this.props.state
- return(
- ...
- <Input value={state.text1} beforeChange={this.handleTextBeforeChange} />
- ...
- )
- }
- ...
Input 组件的属性 useComposedValue 开启后, 可以实现在输入法输入拼音的过程中绑定的值不会更新的功能, 等拼音输入完成后 state 值才更新, 减少拼音输入过程中不必要的 state 更新和视图渲染动作:
useComposedValue 效果
useComposedValue 属性目前为实验特性, 如果发现在某个浏览器或某种输入法中有问题, 欢迎提交 https://github.com/BirdLeeSCUT/pastate/issues .
Checkbox 复选框
每个 Checkbox 是一个勾选框组件, 只需传递要绑定的布尔值 state 节点, 即可完成绑定:
- import { ..., Checkbox } from 'pastate'
- ...
- render(){
- let state = this.props.state
- return(
- ...
- I am a boy: <Checkbox value={state.isBoy} />
- ...
- )
- }
- ...
复选框
Checkbox 组件的属性及其说明如下:
属性 | 值 | 说明 |
---|---|---|
value | boolean,必填 | 绑定的数据值,可直接传入 this.props.state 中的节点值, 无需 做 state.xxx == true 转化。 |
afterChange | (newValue: boolean) => void | 在绑定值更新后会被调用 |
disabled | boolean | 指定禁止点击状态,默认为 false |
className | string | 传递 class 名 ( 用于指定 css 样式等 ) |
id | string | 传递 id 名 ( 用于指定 css 样式等 ) |
RadioGroup 单选框选项组
RadioGroup 是一个单选框选项组, 只要传入选项数组 options 常数和要绑定的选项值 value, 即可完成绑定:
- import { ..., RadioGroup } from 'pastate'
- const goodNames = ['Peter', 'Tom', 'Allen']
- ...
- render(){
- let state = this.props.state
- return(
- ...
- Choose a name: <RadioGroup options={goodNames} value={state.name}/>
- ...
- )
- }
- ...
单选框选项组
RadioGroup 组件的属性及其说明如下:
属性 | 值 | 说明 |
---|---|---|
options | Array<string | number | boolean> | Array<{value: string | number | boolean, tag: string, disabled?: boolean}>, 必填 | 选项数据数组 |
value | string | number | boolean,必填 | 绑定的选中值 |
disabled | boolean | 指定禁止选择状态,默认为 false |
vertical | boolean | 指定选项为垂直排列状态,默认为 false |
afterChange | (newValue: string | number | boolean) => void | 在绑定值更新后会被调用 |
id | string | 传递给选项组根元素的 id |
className | string | 传递给选项组根元素的 className |
radioClassName | string | 传递给圆形按钮的 className |
tagClassName | string | 传递给选项标签的 className |
disabledTagClassName | string | 传递给禁用状态的选项标签的 < strong ow="112" oh="37"> 附加 的 className |
options 属性接受两种格式的选项数据格式:
- // 简单格式: Array<string>
- const nameOptions = ['Peter', 'Tom', 'Allen']
- // 完备格式: Array<{value: string, tag: string, disabled?: boolean}>
- const nameOptionsChinese = [{
- value: 'Peter', // 数据值
- tag: '彼得', // 选项标签值
- disabled: true // 可选地指定某个选项为不可选
- },{
- value: 'Tom',
- tag: '汤姆'
- },{
- value: 'Allen',
- tag: '艾伦'
- }]
RadioGroup 是基于 PureComponent 实现的, 对于 options 属性的值, 建议定义一个 文件级 的选项数组常量, 这样可以提高渲染效率. 如果把 options 值定义在 render 函数里或写成直接赋值的匿名对象(
<RadioGroup options={["a", "b"]} ...>
), 在每次父组件渲染时, 无论绑定的 value 数据有没有更新, RadioGroup 获取到的 options 属性的引用值都不一样, 会使 RadioGroup 进行多余的渲染动作. 当你使用其他基于 PureComponent 实现的组件时, 在向其传递数组 / 对象类型的常量的时候也可以做这样的性能优化.
Select 下拉选择框
Select 是一个选择框组件, 使用方法与 RadioGroup 类似, 只要传入选项数组和要绑定的选择值即可完成绑定:
- import { ..., Select } from 'pastate'
- const nameOptionsChinese = [{
- value: 'DEFAULT',
- tag: '请选择',
- disabled: true
- },{
- value: 'Peter',
- tag: '彼得'
- }, {
- value: 'Tom',
- tag: '汤姆'
- }, {
- value: 'Allen',
- tag: '艾伦'
- }]
- ...
- render(){
- let state = this.props.state
- return(
- ...
- Choose a name: <Select options={nameOptionsChinese} value={state.name}/>
- ...
- )
- }
- ...
下拉选择框
不同操作系统, 不同浏览器的下拉样式不一样
Seclect 组件的属性及其说明如下:
属性 | 值 | 说明 |
---|---|---|
options | Array<string | number | boolean> | Array<{value: string | number | boolean, tag: string, disabled?: boolean}>, 必填 | 选项数据数组 |
value | string | number | boolean,必填 | 绑定的选中值 |
disabled | boolean | 指定禁止点击状态,默认为 false |
afterChange | (newValue: string | number | boolean) => void | 在绑定值更新后会被调用 |
id | string | 传递的 id |
className | string | 传递的 className |
如有需要显示没有选择的状态, 可以多设置一个选项元素, 通过元素的 tag 设置其提示文本, 并把元素的 disabled 设为 true 即可, 没选中时的 value 值自行定义, 不可为 null 或 undefined:
- const nameOptionsChinese = [{
- value: 'DEFAULT',
- tag: '请选择',
- disabled: true
- }, ... ]
Seclect 组件目前仅支持最常用的单选功能, 以后将支持多选功能.
pastate 表单组件的类型定义
Pastate 的高阶组件均是使用 Typescript 进行开发 , 提供完整的类型定义文件, 当你在 javascript 项目使用它们时, 也可以得到良好的组件属性 intelliSense 类型提示:
组件的属性类型提示
你也可以右键点击组件名, 选择转到类型定义, 查看组件的所有属性声明:
选择转到类型定义
组件的类型定义文件
上图的 type 属性的类型是枚举字符串, 在输入时, 你可以在空双括号中按下 vsCode 的 "触发提示" 快捷键 (具体快捷键因不同系统操作系统和不同设置而异, 请到编辑器的 "首选项 -> 设置快捷方式" 处查看, 该功能很实用):
属性值提示
对现有组件库进行双向数据绑定
很多时候, 我们会使用 react 视图组件库来开发应用, 如 https://ant.design , http://www.material-ui.com 等, pastate 为这些现有的视图组件提供两个数据双向绑定方法! 下面我们以 ant.design 的 Rate https://ant.design/components/rate-cn/ 星级评分组件为例进行介绍.
使用 Bind 高阶组件
你可以使用 pastate 提供的 Bind 组件去包围一个原始的视图组件, 实现双向数据绑定.
我们先简单地安装 ant.design 组件库:
$ npm install antd --save
或 $ yarn add antd, 引入 Rate 组件, 并使用 Bind 组件进行包装, 实现双向数据绑定:
- import { ..., Bind } from 'pastate';
- import Rate from 'antd/lib/rate';
- import 'antd/lib/rate/style/css';
- // 或经过简单配置后, 使用 import { Rate } from 'antd', 详见 ant.design 文档
- ...
- // 我们使用 state.basicInfo.age 数据进行演示
- class BasicInfoView extends PureComponent {
- ...
- render() {
- /** @type {initState['basicInfo']} */
- let state = this.props.state;
- return (
- <div style={{ padding: 10, margin: 10 }}>
- ...
- <div>
年龄(Bind):
- <Bind value={state.age}>
- <Rate />
- </Bind>
- </div>
- ...
- </div>
- )
- }
- }
我们使用 Bind 组件对 Rate 组件进行包装, 并把本来需要传递给 Rate 组件的 value 值传递通过 Bind 组件进行传递, 这样就实现了双向数据绑定! 如果我们要传递其他属性值给 Rate, 可以不通过 Bind 直接传递给 Rate, 如下:
- ...
- <Bind value={state.age}>
- <Rate count={10} /> {/* 根据 ant design 文档, count 属性指定采用 n 级评分 */}
- </Bind>
- ...
这样我们就实现了对 Rate 进行双向数据绑定:
成功绑定 Rate 组件到 age 数据
你可以通过点击 Rate 组件的星星或点击 descrease age ,increase age 按钮对年龄值进行改变, 可以发现当通过按钮更新数据时, Rate 组件可以正确响应. 而且 Bind 是一个关于 value 的纯组件, 当其他无关数据发生改变时, Bind 元素不会发生多余的渲染动作.
Bind 元素还有两个可选的属性:
valueProp: 指定被包装的组件接收数据的属性名, 默认是 value, 绝大多数组件也是用 value, 因此无需指定该值. 如果被包装的组件使用的是 checked 或其他属性名接收数据值, 那么请把 Bind 元素的 valueProp 设为对应的 checked 或其他属性名.
afterChange: 当组件绑定的值通过该组件发生改变后, 会调用该函数, 该函数的签名为 (newValue) => void. 当绑定的属性值不是通过该组件发生改变时, afterChange 函数不会被调用.
使用 makeBindable 函数
你也可以使用
makeBindable(RawComponent, valueProp = 'value')
函数生成一个可以复用的可绑定组件:
- import {...,
- makeBindable
- }
- from 'pastate';...
- // 使用 makeBindable 生成对应的可以绑定数据的 Rate 组件
- const MyRate = makeBindable(Rate) class BasicInfoView extends PureComponent {...render() {
- /** @type {initState['basicInfo']} */
- let state = this.props.state;
- return ( < div style = {
- {
- padding: 10,
- margin: 10
- }
- } > ... < div >
年龄(makeBindable):
- <MyRate count={10} value={state.age}/>
- </div>
- ...
- </div>
- )
- }
- }
你可以通过 makeBindable 函数的第二个参数指定 valueProp 值, 如
const MyCheckbox = makeBindable(Checkbox, 'checked')
, 同样, 你可以通过新组件的 afterChange 属性去响应组件值更新后的个性化操作.
对于绑定的值为空的情况
无论使用 pastate 的表单输入组件还是包装现有的组件库, 对于组件绑定的数据值, 不支持被设为 null 值 或 undefind 值. 通常情况下, 我们不会有这种需求. 如果需要实现表单的 "未选择" 状态, 我们一般通过设置一个默认值且不可选的 default 值来代替. 例如有个表单需要选择性别, 并且需要一个未选择的状态, 这样使用一个 RadioGroup 组件来实现:
- let initState = {
- sex: ''// 用字符串表示性别, 并设置为选择状态是的值为空字符串'' 或'default'
- }
- const sexOptions = ['boy', 'girl'] // 只包含目标值
- ...
- render(){
- let state = this.props.state;
- return <RadioGroup options={sexOptions} value={state.sex} />
- }
- ...
如果你的需求场景一定要用到 null 或 undefined 值, 欢迎在 https://github.com/BirdLeeSCUT/pastate/issues 中分享. Pastate 能实现绑定的值支持空值, 但如果这个需求只有在非常特殊的情况下才用到, 我们就暂不把它默认实现在 pastate 库中, 因为这会增加计算量, 你可以自行实现这个特殊组件的数据绑定逻辑.
下一章, 我们来看看 pastate 应用如何进行模块化.
来源: http://www.jianshu.com/p/16b264a61da2