组件与服务器通讯
组件向服务器提交数据一定是由组件UI的某一事件触发的,所以只要在监听相应事件的回调函数中执行向服务器提交数据的逻辑即可。
组件挂载阶段通讯
componentDidMount是执行服务器通讯的最佳地方,原因如下:
- componentDidMount中执行服务器通讯可以保证获取到数据时,组件已经处于挂载状态,这是即使要直接操作DOM也是安全的,而componentWillMount是无法保证的;
- 当组件在服务器端渲染时,componentWillMount会被调用两次,一次是在服务器端,另一次是在浏览器端,而componentDidMount能保证在任何情况下只会被调用一次,从而不会发送多余的数据请求;
示例代码:
import React from 'react';
class UserListReq extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null
}
}
componentDidMount() {
const req = this;
//请求参数
const reqParam = {
number: 10
}
fetch('http://localhost:8080/test/getUser',{
//请求方式
method: 'POST',
//请求参数
body: JSON.stringify(reqParam),
//请求头
headers: {
'content-type': 'appliction/json'
}
})
.then(function (resp) {
req.setState({
data: resp
});
});
}
render() {
return(
<div>
{this.state.data}
</div>
);
}
}
export default UserListReq;
需要进行跨域处理。
组件更新阶段通讯
执行具体的请求操作与componentDidMount方法的做法相同,但是需要进行新旧数据比对,减少重复请求。
import React from 'react';
class UserListReq extends React.Component {
constructor(props) {
super(props);
this.state = {
id: 0,
data: null
}
}
static getDerivedStateFromProps(nextProps,prevState){
//该方法内禁止访问this
if(nextProps.id !== prevState.id){
//通过对比nextProps和prevState,返回一个用于更新状态的对象
return {
value:nextProps.data
}
}
//不需要更新状态,返回null
return null
}
render() {
return(
<div>
{this.state.data}
</div>
);
}
}
export default UserListReq;
componentWillReceiveProps已经不建议使用,可以使用getDerivedStateFromProps来代替。
组件通讯
组件间的通讯主要分为以下几种。
父子组件间的通讯
父子组件之间通讯主要依靠props。
父组件向子组件传递信息,示例代码:
父组件
import React from 'react';
import UserList from './UserList';
class UserListContainer extends React.Component{
constructor(props) {
super(props);
this.state = {
users: []
}
this.timer = null;
this.getData = this.getData.bind(this);
}
componentDidMount() {
this.timer = setInterval(this.getData, 2500);
}
componentWillUnmount() {
clearInterval(this.timer);
}
getData() {
this.setState({
users: [
{
'id': 0,
'name': 'a'
},
{
'id': 1,
'name': 'b'
},
{
'id': 2,
'name': 'c'
},
{
'id': 3,
'name': 'd'
},
{
'id': 4,
'name': 'e'
},
{
'id': 5,
'name': 'f'
},
{
'id': 6,
'name': 'g'
}
]
})
}
render() {
return(<UserList users={this.state.users}/>);
}
}
export default UserListContainer;
子组件:
import React from 'react';
class UserList extends React.Component {
render() {
return(
<div>
<ul className="user-list">
{
this.props.users.map(function (user) {
return (
<li key={user.id}>
<span>{user.name}</span>
</li>
);
})
}
</ul>
</div>
);
}
}
export default UserList;
与父组件传递信息到子组件直接使用props不同,子组件传递信息到父组件需要声明一个回调方法。
示例代码:
父组件:
import React from 'react';
import UserList from './UserList';
class UserListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
users: []
}
this.handleAddUser = this.handleAddUser.bind(this);
}
componentDidMount() {
this.setState({
users: [
{
'id': 0,
'name': 'a'
},
{
'id': 1,
'name': 'b'
},
{
'id': 2,
'name': 'c'
},
{
'id': 3,
'name': 'd'
},
{
'id': 4,
'name': 'e'
},
{
'id': 5,
'name': 'f'
},
{
'id': 6,
'name': 'g'
}
]
})
}
//处理子组件的添加操作
handleAddUser(user) {
const preData = this.state.users;
this.setState({
users:[...preData, {'id': null, 'name': user}]
});
}
render() {
return(<UserList users={this.state.users} onAddUser={this.handleAddUser}/>);
}
}
export default UserListContainer;
子组件:
import React from 'react';
class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
newUser: ''
}
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleChange(e) {
this.setState({
newUser: e.target.value
});
}
handleClick() {
if(this.state.newUser && this.state.newUser.length > 0) {
//调用回调函数,回调函数由父组件传入
this.props.onAddUser(this.state.newUser);
}
}
render() {
return(
<div>
<ul className="user-list">
{
this.props.users.map(function (user, index) {
return (
<li key={index}>
<span>{user.name}</span>
</li>
);
})
}
</ul>
<input onChange={this.handleChange} value={this.state.newUser}/>
<button onClick={this.handleClick}>添加新用户</button>
</div>
);
}
}
export default UserList;
兄弟组件间的通讯
当两个组件不是父子关系但拥有相同的父组件是,称之为兄弟组件。
兄弟组件在整个组件树上并不一定处于同一个层级。
兄弟组件不能直接进行数据传递,需要通过状态提升的方式实现兄弟组件的通讯,即把组件之间需要共享的状态保存到距离它们最近的共同父组件内,任意一个兄弟组件都可以通过父组件传递的回调方法来修改共享状态,父组件中共享状态的变化也会通过props向下转递给所有兄弟组件,从而完成兄弟组件之间的通信。
兄弟组件A,User List:
import React from 'react';
class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
newUser: ''
}
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleUserClick = this.handleUserClick.bind(this);
}
handleChange(e) {
this.setState({
newUser: e.target.value
});
}
handleClick() {
if(this.state.newUser && this.state.newUser.length > 0) {
//调用回调函数,回调函数由父组件传入
this.props.onAddUser(this.state.newUser);
}
}
//将所选元素信息传递到父组件
handleUserClick(index) {
this.props.onSetCurrentUser(index);
}
render() {
const that = this;
return(
<div>
<ul className="user-list">
{
this.props.users.map(function (user, index) {
return (
<li key={index}
onClick={that.handleUserClick.bind(that, index)}>
<span>{user.name}</span>
</li>
);
})
}
</ul>
<input onChange={this.handleChange} value={this.state.newUser}/>
<button onClick={this.handleClick}>添加新用户</button>
</div>
);
}
}
export default UserList;
兄弟组件B,UserDetail:
import React from 'react';
function UserDetail(props) {
return(
<div>
{
props.currentUser ? <div>这是{props.currentUser == null ?
'未选择用户' : props.currentUser.name}的详细信息</div> : ''
}
</div>
);
}
export default UserDetail;
父组件,User Container:
import React from 'react';
import UserDetail from './UserDetail';
import UserList from './UserList';
class UserListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [],
currentUserIndex: null
}
this.handleAddUser = this.handleAddUser.bind(this);
this.handleSetCurrentUser = this.handleSetCurrentUser.bind(this);
}
componentDidMount() {
this.setState({
users: [
{
'name': 'a'
},
{
'name': 'b'
},
{
'name': 'c'
},
{
'name': 'd'
},
{
'name': 'e'
},
{
'name': 'f'
},
{
'name': 'g'
}
]
})
}
//处理子组件的添加操作
handleAddUser(user) {
const preData = this.state.users;
this.setState({
users:[...preData, {'id': null, 'name': user}]
});
}
//设置被选中元素
handleSetCurrentUser(index) {
this.setState({
currentUserIndex: index
});
}
render() {
const aimUser = this.state.users[this.state.currentUserIndex];
return(
<div>
<UserList
users={this.state.users}
onAddUser={this.handleAddUser}
onSetCurrentUser={this.handleSetCurrentUser}/>
<h1>信息显示:</h1>
<UserDetail currentUser={aimUser}/>
</div>
);
}
}
export default UserListContainer;
深层级组件间的通讯
当组件所处层级太深时,往往需要经过多层的props传递才能将所需的数据或者回调函数传递给使用组件。
此时就需要Context来传递各种信息。
创建Context的方式是:在提供context的组件内新增一个getChildContext方法,返回context对象,然后在组件的childContextTypes属性上定义context对象的属性的类型信息。
使用Context的方式是:当任意层级的子组件需要使用时,只需要在该组件的contextTypes中声明使用的context属性即可。
示例代码:
import React from 'react';
function UserDetail(props) {
return(
<div>
{
props.currentUser ? <div>这是{props.currentUser == null ?
'未选择用户' : props.currentUser.name}的详细信息</div> : ''
}
</div>
);
}
export default UserDetail;
示例代码:
import React from 'react';
import PropTypes from 'prop-types';
class UserAdd extends React.Component {
constructor(props) {
super(props);
this.state = {
newUser: ''
}
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleChange(e) {
this.setState({
newUser: e.target.value
});
}
handleClick() {
if(this.state.newUser && this.state.newUser.length > 0) {
//调用回调函数,回调函数由父组件传入
this.context.onAddUser(this.state.newUser);
}
}
render() {
return(
<div>
<input onChange={this.handleChange} value={this.state.newUser}/>
<button onClick={this.handleClick}>添加新用户</button>
</div>
);
}
}
//声明要使用的context对象的属性
UserAdd.contextTypes = {
onAddUser: PropTypes.func
}
export default UserAdd;
示例代码:
import React from 'react';
import UserAdd from './UserAdd';
class UserList extends React.Component {
handleUserClick(index) {
this.props.onSetCurrentUser(index);
}
render() {
const that = this;
return(
<div>
<ul className="user-list">
{
this.props.users.map(function (user, index) {
return (
<li key={index}
onClick={that.handleUserClick.bind(that, index)}>
<span>{user.name}</span>
</li>
);
})
}
</ul>
<UserAdd onAddUser={this.props.onAddUser}/>
</div>
);
}
}
export default UserList;
示例代码:
import React from 'react';
import PropTypes from 'prop-types';
import UserDetail from './UserDetail';
import UserList from './UserList';
class UserListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [],
currentUserIndex: null
}
this.handleAddUser = this.handleAddUser.bind(this);
this.handleSetCurrentUser = this.handleSetCurrentUser.bind(this);
}
componentDidMount() {
this.setState({
users: [
{
'name': 'a'
},
{
'name': 'b'
},
{
'name': 'c'
},
{
'name': 'd'
},
{
'name': 'e'
},
{
'name': 'f'
},
{
'name': 'g'
}
]
})
}
//处理子组件的添加操作
handleAddUser(user) {
const preData = this.state.users;
this.setState({
users:[...preData, {'id': null, 'name': user}]
});
}
//设置被选中元素
handleSetCurrentUser(index) {
this.setState({
currentUserIndex: index
});
}
//创建context对象
getChildContext() {
return {
onAddUser: this.handleAddUser
};
}
render() {
const aimUser = this.state.users[this.state.currentUserIndex];
return(
<div>
<UserList
users={this.state.users}
currentUserIndex={this.state.currentUserIndex}
onSetCurrentUser={this.handleSetCurrentUser}/>
<h1>信息显示:</h1>
<UserDetail currentUser={aimUser}/>
</div>
);
}
}
//声明context的属性的类型信息
UserListContainer.childContextTypes = {
onAddUser: PropTypes.func
};
export default UserListContainer;
特殊的ref
ref不仅可以用来获取表单元素,还可以用来获取其他任意DOM元素,甚至可以用来获取React组件实例。
但是,应该避免使用ref,因为ref破坏了React中以props为数据传递介质的典型数据流。
在DOM元素上使用ref
在DOM元素上使用ref是最常见的使用场景。ref接收一个回调函数作为值,在组件被挂载或卸载时,回调函数会被调用,在组件被卸载时,回调函数会接收当前DOM元素作为参数;在组件被卸载时,回调函数会接收null作为参数。
import React from 'react';
class AutoFocusTextInput extends React.Component {
//完成挂载之后,调用焦点方法
componentDidMount() {
this.textInput.focus();
}
//通过ref,为对象赋值
render() {
return(
<div>
<input type="text" ref={(input) => {
this.textInput = input;
}}/>
</div>
);
}
}
export default AutoFocusTextInput;
在组件上使用ref
React组件也可以定义ref,此时ref的回调函数接收的参数是当前组件的实例,这提供了一种从组件外部操作组件的方式。
定义组件:
import React from 'react';
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.blur = this.blur.bind(this);
}
//完成挂载之后,调用焦点方法
componentDidMount() {
this.textInput.focus();
}
blur() {
this.textInput.blur();
}
//通过ref,为对象赋值
render() {
return(
<div>
<input type="text" ref={(input) => {
this.textInput = input;
}}/>
</div>
);
}
}
export default AutoFocusTextInput;
使用组件:
import React from 'react';
import AutoFocusTextInput from './AutoFocusTextInputB';
class Container extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
//通过ref调用组件的方法
this.inputInstance.blur();
}
render() {
return(
<div>
<AutoFocusTextInput ref={(input) => {this.inputInstance = input;}} />
<button onClick={this.handleClick}>失去焦点</button>
</div>
);
}
}
export default Container;
父组件访问子组件的DOM节点
在一些场景下,可能需要父组件中获取子组件中的某个DOM元素,这时候直接使用ref是没法实现的,因为ref只能获取子组件的实例对象,而不能获取子组件中的某个DOM元素。
可以使用传递的方式:在子组件的DOM元素上定义ref,ref的值是父组件传递给子组件的一个回调函数,回调函数可以通过一个自定义的属性传递,这样父组件就可以通过回调函数获取该DOM元素。
实例代码:
import React from 'react';
function Children(props) {
//为父组件的属性赋值
return(
<div>
<input ref={props.inputRef}/>
</div>
);
}
class Parent extends React.PureComponent {
render() {
return(
<Children inputRef={el => this.inputElement = el}/>
);
}
}
export default Parent;