受控组件
在 HTML 中,表单元素(如<input>
、 <textarea>
和 <select>
)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。
我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
当然获取表单值的实现方式是灵活的.
class HelloWorld extends React.Component {
constructor(props) {
super(props);
//保存表单的值
this.state = {
username: '默认用户名',
exp: '0'
}
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e){
// 阻止默认提交行为
e.preventDefault();
// 获取表单内容
console.log(this.state);
}
handleInputChange(inputName,e){
this.setState({
[inputName]: e.target.value
})
}
render(){
return (
<div>
<form action="/abc" onSubmit={this.handleSubmit}>
姓名: <input type="text" name="username" value={this.state.username} onChange={e=> this.handleInputChange('username',e)}/><br/>
经验: <select name="exp" value={this.state.exp} onChange={e=> this.handleInputChange('exp',e)}>
<option value="0">无</option>
<option value="1">1-3年</option>
<option value="2">3-5年</option>
</select>
<input type="submit" value="提交" />
</form>
</div>
)
}
}
// 通过调用React自身方法render可以得到当前组件的实例对象,并渲染到页面容器.
ReactDOM.render(<HelloWorld />,document.getElementById('root'));
状态提升
通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。让我们看看它是如何运作的。
案例说明:
两个子组件都可以输入薪资,单位分别是人民币(CNY)和美金(USD). 其中一个被输入,同时会被换算(汇率按照6.65)成另一个币种显示换算后的结果. 两个组件输入的值会相互影响. 同时外部组件会根据薪资判断是否是”有钱人”.
在这里两个子组件的input值都是分别被另一个子组件共享的,所以这个值我们把它提升到父级组件. 通过对父级组件的修改,从而影响另一个组件的输入. 这个就是input.value的状态提升.
代码部分:
class Salary extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(e.target.value);
}
render() {
return (
<div>
<fieldset>
<legend>薪资{this.props.cType}:</legend>
<input onChange={this.handleChange} value={this.props.cValue||''}/>
{this.props.cTypeIcon}
</fieldset>
</div>
);
}
}
class HelloWorld extends React.Component {
constructor(props){
super(props);
this.state = {
type: 'CNY',
salary: 0
},
this.reverse = this.reverse.bind(this);
this.handleCNYChange = this.handleCNYChange.bind(this);
this.handleUSDChange = this.handleUSDChange.bind(this);
}
componentDidUpdate(){
console.log(this.state);
}
reverse(type,salary){
// 费率
const C = 6.65;
var outValue = 0;
var inputValue = parseFloat(salary);
if(Number.isNaN(inputValue)){
return '';
}
// 转换成人民币
if(type == 'CNY'){
outValue = salary*C;
}else{
outValue = salary/C;
}
// 四舍五入 保留2位
var roundValue = Math.round(outValue*100)/100;
// 格式化
if(roundValue.toString().indexOf('.')==-1){
roundValue += '.00';
}else{
//小数部分
var dotNumber = roundValue.toString().split('.')[1];
dotNumber = dotNumber.length==1? dotNumber+'0': dotNumber;
roundValue = roundValue.toString().split('.')[0]+'.'+dotNumber;
}
return roundValue;
}
// 人民币转换美元
handleCNYChange(salary){
this.setState({
type: 'CNY',
value: salary
})
}
handleUSDChange(salary){
this.setState({
type: 'USD',
value: salary
})
}
// 判断是否是高收入
areUOk(){
// 人民币高于1000算高收入
if(this.state.type == 'CNY'){
return this.state.value>1000;
}else{
return this.reverse('CNY',this.state.value)>1000;
}
}
render() {
var type = this.state.type;
var value = this.state.value;
var CNY_value = type=='CNY'? value: this.reverse('CNY',value);
var USD_value = type=='USD'? value: this.reverse('USD',value);
return(
<div>
<Salary key="1" onChange={this.handleCNYChange} cValue={CNY_value} cType="人民币" cTypeIcon="¥"/>
<Salary key="2" onChange={this.handleUSDChange} cValue={USD_value} cType="美元" cTypeIcon="$"/>
<p>{this.areUOk()?'您真是有钱人!':'你还需要加油啊!'}</p>
</div>
)
}
}
组合vs继承
React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用。
props.children
有些组件无法提前知晓它们子组件的具体内容。在 Sidebar
(侧边栏)和 Dialog
(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。
我们建议这些组件使用一个特殊的 children
prop 来将他们的子组件传递到渲染结果中:
class SideBarItems extends React.Component {
render(){
return (
<ul>
<li>首页</li>
<li>用户管理</li>
<li>商品管理</li>
<li>内容管理</li>
</ul>
)
}
}
class SideBar extends React.Component {
constructor(props){
super(props);
}
render(){
return (
<div>
{this.props.children}
</div>
)
}
}
class Content extends React.Component {
render(){
return (
<div>
这是内容部分
</div>
)
}
}
class FootBar extends React.Component {
render(){
return (
<div>
这是底部部分
</div>
)
}
}
class BaseLayout extends React.Component {
constructor(props){
super(props);
}
render(){
return (
<div>
{/*props.children是默认熟悉,指当前组件标签的内容部分*/}
{this.props.children}
</div>
)
}
}
class HelloWorld extends React.Component {
render(){
return (
<div>
{/*类似于vue的插槽slot */}
<BaseLayout>
<SideBar>
<SideBarItems/>
</SideBar>
<Content/>
<FootBar/>
</BaseLayout>
</div>
)
}
}
// 通过调用React自身方法render可以得到当前组件的实例对象,并渲染到页面容器.
ReactDOM.render(<App />, document.getElementById("root"));
具名插槽
少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用children,而是自行约定:将所需内容传入 props,并使用相应的 prop。
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
特例关系
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}