@H_502_6@在上篇中,我们介绍了什么是FlexBox布局,以及如何使用FlexBox布局。还没有看过的小伙伴欢迎回到文章列表点击查看之前的文章了解。 @H_502_6@ @H_502_6@那么,当我们有了基本的布局概念之后,就可以做一些有意思的尝试了。不过,它们会有一个美中不足:只是静静地呆在那里,不接受反馈。换句话说,它们从应用开始到结束,只有一种状态。 @H_502_6@ @H_502_6@注意,上面这句话其实包含了RN中(当然同时也是React中)两个非常重要的概念:本文由葡萄城技术团队于博客园原创并首发
- 第一,“从应用开始到结束”,意味着它在时间上有一段生命周期(Life Cycle)。
- 第二,应用其实可以拥有很多种状态(State),比如,正常时是一种状态,出错时是另一种状态。而且这些状态能够在某些条件下进行转换。
基本概念:
@H_502_6@在RN中,界面的变化对应着程序状态的变化。或者说,界面的变化,正是因为应用的状态发生了转换而导致的。应用的状态主要由两个变量决定,props和state,它们可以存在于继承自React.Component的每一个组件中。state由组件自身定义,用来管理组件及其子组件的状态。而props来自于父组件,在本组件中相当于常量,它的改变方式只能来自于父组件。 @H_502_6@在RN中,界面的变化对应着程序状态的变化。或者说,界面的变化,正是因为应用的状态发生了转换而导致的。应用的状态主要由两个变量决定,props和state,它们可以存在于继承自React.Component的每一个组件中。state由组件自身定义,用来管理组件及其子组件的状态。而props来自于父组件,在本组件中相当于常量,它的改变方式只能来自于父组件。 @H_502_6@ @H_502_6@state和props都不允许手动地直接设值。像this.state.a = 1或者this.props.b = 2这种代码是会报错的。要改变state,只能是在本组件中调用this.setState方法。而要改变props,只能依赖于它的值在传下来之前,已经在其父组件中被改变。 @H_502_6@ @H_502_6@既然在组件中,state属性无论从字面含义还是程序语义上,都是用来表示状态的,那么为什么还需要一个props属性呢? @H_502_6@我的理解主要有两个原因。 @H_502_6@第一,因为有些组件其实是“无状态”的。它们只是接受父组件传给它们的东西,然后老老实实把它们渲染出来。它们自己内部不保存任何状态,它们只是对父组件状态的反应。或者说:“它们不生产状态,它们只是父组件状态的显示器。”父组件的状态通过props传递给子组件。我们经常会构造这种无状态的组件,因为它职责单一,封装性好,可作为更复杂组件的基石。 @H_502_6@第二,由于父组件与子组件之间往往需要联动,props就是最直接的提供联动的手段。父组件中构造子组件时,就像函数调用的传参一样,把需要的东西传给子组件的props。 @H_502_6@ @H_502_6@state和props的重要特点是,默认情况下。当它们改变时,RN会自动东西渲染与之相关的界面以保持和state与props同步。为什么说“默认情况下”,是因为我们可以利用生命周期函数手动“截断”这个渲染逻辑,本文暂不涉及。 @H_502_6@ @H_502_6@另外,在RN中,其实也可以使用不属于props和state的变量,来手动控制组件的状态。但是不推荐这么做。因为这会使状态的控制方法变得不统一,不利于后期维护。 @H_502_6@开始尝试:
@H_502_6@我们已经可以基于state与props的概念做一个小练习了。它是一个ToDo List,也就是待办列表。大概长下面这个样子:To Do List草图
@H_502_6@ @H_502_6@我们把它分为两个页面。最左边是添加待办事项的界面,记为ToDoListAdd。中间和最右边其实是同一个界面,记为ToDoListMain,它拥有两种不同状态。 @H_502_6@ @H_502_6@我们先来看ToDoListAdd界面。它有上中下三个部分。最上面是一个可点击返回的头部,中间是用于输入文字的TextInput,底部是一个确认添加的Button。 @H_502_6@ @H_502_6@有没有发现它和上一次我们的flexBox小练习界面很像呢?没错,基于上一篇的布局知识,我们可以方便地把页面修改成这样。 @H_502_6@ @H_502_6@再来看ToDoListMain界面,它与ToDoListAdd很像。头部的"添加"用以跳转至ToDoListAdd。“多选”用以让每一个待办项的CheckBox显示出来,并且显示最下面包含全选CheckBox的footer。 @H_502_6@ @H_502_6@要完整地完成这个应用,本文的篇幅是不够的,后续文章会深入到更加细节的地方。但是首先,本文会介绍如何实现以下基本功能:1.利用state控制编辑状态;2.利用state实现界面的跳转;3.父子组件之间的通信。让我们着手编写程序,穿插着介绍着三点内容。 @H_502_6@ @H_502_6@步骤1,使用flex布局完成ToDoListAdd界面。在根目录新建一个文件ToDoListAdd.js,定义ToDoListAdd类。为更加简洁,这里省去必要组件的引入代码,以及样式代码。export default class ToDoListAdd extends Component<Props> {
constructor(props) {
super(props);
}
onPress() { } // 暂且为空
render() {
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.add} onPress={this.props.onBack}>返回</Text>
</View>
<View style={styles.body}>
<TextInput />
</View>
<View style={styles.footer}>
<Button title="确定" onPress={this.onPress} style={styles.btn} />
</View>
</View>
);
}
}
export default class ToDoListmain extends Component<Props> {
constructor(props) {
super(props);
this.state = {
isEditing: false
};
this.onEdit = this.onEdit.bind(this);
this.renderItem = this.renderItem.bind(this);
}
renderFooter(toggleCheckAll,isAllChecked) {
if (!this.state.isEditing) {
return null;
}
const count = this.props.todoList.filter(item => item.isChecked).length;
return (
<View style={styles.footer}>
<CheckBox onValueChange={toggleCheckAll} value={isAllChecked} />
<Text style={styles.menu}>{`已选择:${count}项`}</Text>
</View>
);
}
onEdit() {
this.setState((prevState) => {
return {
isEditing: !prevState.isEditing
}
});
}
renderItem(item) {
return (<ToDoListItem {...item}
toggleItemCheck={this.props.toggleItemCheck}
isEditing={this.state.isEditing} />);
}
render() {
const { toggleCheckAll,isAllChecked,onAddItem,todoList } = this.props;
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.add} onPress={onAddItem}>添加</Text>
<Text style={styles.title}>待办列表</Text>
<Text style={styles.multi}
onPress={this.onEdit}>{this.state.isEditing ? '取消' : '多选'}
</Text>
</View>
<FlatList style={styles.body} isEditing={this.state.isEditing}
data={todoList} renderItem={this.renderItem} />
{this.renderFooter(toggleCheckAll,isAllChecked)}
</View>
);
}
}
步骤3,实现ToDoListItem组件。它没有自己的状态,也只是对父组件内容的展示。
export default class ToDoListItem extends Component<Props> {
constructor(props) {
super(props);
}
render() {
const { toggleItemCheck,item } = this.props;
const { isChecked,detail,level } = item;
const basicLevelStyle = styles.level;
let specificLevelStyle;
if (level === 'info') {
specificLevelStyle = styles.info;
} else if (level === 'warning') {
specificLevelStyle = styles.warning;
} else {
specificLevelStyle = styles.error;
}
return (
<View style={styles.container}>
{this.props.isEditing && <CheckBox onValueChange={(value) => toggleItemCheck(item,value)} style={styles.checkBox} value={isChecked} />}
<Text style={styles.detail}>{detail}</Text>
<View style={[basicLevelStyle,specificLevelStyle]}></View>
</View>
);
}
}
特别是,每一项是否被check,这个状态其实来自于todoList数据源,而每一项的CheckBox的value完全受控于父组件传来的值,所以这种用户输入型的组件,其值完全受控于父组件的props的传值的,也常被称为受控组件(Controlled Component)。
@H_502_6@ @H_502_6@另外,todoList的每一项,我们用level来表示待办项的某种等级,用detail表示它的内容,用isChecked来表示它是否完成。 @H_502_6@ @H_502_6@但是做了这么多,我们还啥都没看到呢。所以,接下来的关键一步,就是把ToDoListMain和ToDoListAdd的渲染逻辑一口气写到App.js中去。 @H_502_6@步骤4,写最外层的渲染逻辑。在App.js中,引入
import ToDoListMain from './ToDoListMain';
import ToDoListAdd from './ToDoListAdd';
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.state = {
current: 'main',todoList: [
{
level: 'info',detail: '一般的任务',isChecked: false,key: '0'
},{
level: 'warning',detail: '较重要的任务',},{
level: 'error',detail: '非常重要且紧急的任务',key: '2'
}
]
}
this.onAddItem = this.onAddItem.bind(this);
this.onBack = this.onBack.bind(this);
this.toggleCheckAll = this.toggleCheckAll.bind(this);
this.toggleItemCheck = this.toggleItemCheck.bind(this);
}
onAddItem() {
this.setState((prevState) => {
return {
current: 'add'
}
});
}
onBack() {
this.setState({
current: 'main'
});
}
toggleCheckAll() {
const flag = this.isAllChecked();
const newTodos = this.state.todoList.map(item => {
return {
...item,isChecked: !flag
};
});
this.setState({
todoList: newTodos
});
}
toggleItemCheck(item,key) {
const newTodos = this.state.todoList.map(todo => {
if (todo !== item) {
return todo;
}
return {
...todo,isChecked: !item.isChecked
}
});
this.setState({
todoList: newTodos
});
}
isAllChecked() {
if (!this.state.todoList) return false;
if (this.state.todoList.length === 0) return false;
return this.state.todoList.every(item => item.isChecked);
}
render() {
if (this.state.current === 'main') {
return (<ToDoListMain
isAllChecked={this.isAllChecked()}
toggleCheckAll={this.toggleCheckAll}
toggleItemCheck={this.toggleItemCheck}
onAddItem={this.onAddItem}
todoList={this.state.todoList} />);
} else {
return (<ToDoListAdd onBack={this.onBack} />);
}
}
}
好了,让我们运行起程序,看看效果怎么样吧。