使用props
作为属性数据共享的载体,在组件之间传输;但是在react
中这种传输时单向的,即父组件->子组件,想要将子组件中的数据变动让其他组件也接收到,需要做处理。
2019-7-29 更新
- 完善
Context
的嵌套属性传入解决示例; - 完善
Context
动态更新属性解决示例;
Props
props
作为react的基本概念,父组件传输到子组件的属性都保存在props
中,通过this.props
调取其中的属性(如果是通过函数声明,则不需要this
,这个函数接受一个参数props
)。
// child component
function Square(props){
return (
<button
className="square"
onClick={props.onClick}>
{props.name}
</button>
);
}
// child component
class Board extends React.Component{
constructor(props){
super(props);
this.state = {
}
}
render(){
return (<Square name={this.props.name} onClick={()=>this.props.onClick("hello")} />);
}
}
// parent component
class Game extends React.Component {
constructor(props){
super(props);
this.state = {
name:"world"
}
}
handleClick(name){
this.setState({
name:name
});
}
render(){
return (<Board name={this.state.name} onClick={(name)=>this.handleClick(name)} />);
}
}
示例中展示父组件传递一个属性name
给子组件,并传递一个回调函数onClick
,用于修改name
的值,点击按钮时,接收到参数"hello";在处理函数handleClick
中更新父组件的属性name
,从而触发视图的重新渲染,name="hello"
重新传递给子组件,得到视图的更新。
在事件处理的时候,注意
this
对象的绑定,示例中使用了箭头函数()=>{}
解决了该问题。
如果回调是这样传递的
render(){
return (<Square name={this.state.name} onClick={this.handleClick} />);
}
需要在constructor
手动绑定函数的this
对象
constructor(props){
super(props);
this.state = {
name:"world"
}
// so
this.handleClick = this.handleClick.bind(this);
}
在没有关系的的组件间要共享数据,找到他们的公共的父组件处理;但是如果我们的组件嵌套很多,一级一级传递必定会造成困扰。
Context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。 摘自React文档
Context
翻译为上下文,可以理解为给当前的组件this
对象混入一个Context
,类似全局对象,对象是引用类型,属性使用时因为是共享的内存地址;那么属性发生变化时,所有有用的地方属性值都已更改,只要触发重新渲染视图即可(这里已无需考虑,React已经做了这个事情)。
Context 使用的API
-
React.createContext
创建一个Context
,React渲染订阅这个Context对象的组件;在组件树中匹配离自身最近的Provider
中读取当前的Context
值。 -
Context.Provider
生产者;接受一个value
属性,传递给消费组件。 -
Class.contextType
class的contextType
属性挂载一个Context
对象;使用this.context
来使用对象上的值。 -
Context.Consumer
这里需要一个示例
消费者;// child component function Square(){ return (<NameContext.Consumer> {value=>(<button className=""> {value} </button>)} </NameContext.Consumer> ); }
context
属性传递:
// create Context
const NameContext = React.createContext("name");
// child component
class Square extends React.Component{
static contextType = NameContext;
render(){
return (
<button className="square">
{this.context}
</button>
);
}
}
// child component
class Board extends React.Component{
constructor(props){
super(props);
this.state = {
}
}
render(){
return (<Square onClick={()=>this.props.onClick("hello")} />);
}
}
// parent component
class Game extends React.Component {
constructor(props){
super(props);
this.state = {
name:"world"
}
}
handleClick(name){
this.setState({
name:name
});
}
render(){
return (<NameContext.Provider value="admin">
<Board onClick={(name)=>this.handleClick(name)} />
</NameContext.Provider>);
}
}
描述:
createContext
创建这个上下文对象;- 在顶部组件注入作为生产者
.Provider
,接受一个属性传入value
给属性进行赋值。 - 在需要使用这些属性的组件进行混入
contextType
作为非函数组件的属性用于挂载Context
- 获取这些属性
this.context
进行访问。
使用组合模式/嵌套模式来解决大部分跨组件属性传递
// child component
function Square(props){
return (
<button className="square" onClick={props.onClick}>
{props.name}
</button>
);
}
// child component
class Board extends React.Component{
constructor(props){
super(props);
this.state = {
}
}
render(){
return (<div>
<h3>提供测试</h3>
{this.props.btn}
</div>);
}
}
// parent component
class Game extends React.Component {
constructor(props){
super(props);
this.state = {
name:"world"
}
}
handleClick(name){
this.setState({
name:name
});
}
render(){
// 在父组件中创建JSX表达式,作为组件的属性传入,
let btn = (<Square name={this.state.name} onClick={()=>this.handleClick("hello")} />);
return (<Board btn={btn} />);
}
}
描述:
- 把需要用到的组件提到属性定义位置,创建
JSX
表达式,作为属性传入子组件。 - 在需要用到的组件中作为
children
放到合适的位置,有点类似于slot
动态更新Context
属性值
const NameContext = React.createContext({
name:"admin",
updateName:()=>{}
});
// child component
function Square(){
return (<NameContext.Consumer>
{({name,updateName})=>(<button className="" onClick={()=>updateName("hello")}>
{name}
</button>)}
</NameContext.Consumer>
);
}
// child component
class Board extends React.Component{
constructor(props){
super(props);
this.state = {
}
}
render(){
return (<div>
<h3>提供测试</h3>
<Square />
</div>);
}
}
// parent component
class Game extends React.Component {
constructor(props){
super(props);
this.updateName=name=>{
this.setState({
name:name
});
}
this.state = {
name:"admin",
updateName:this.updateName
}
}
handleClick(name){
this.setState({
name:name
});
}
render(){
return (<NameContext.Provider value={this.state}>
<Board />
</NameContext.Provider>);
}
}
描述:
- 创建上下文
Context
属性,更新的方法(空壳方法,在顶层组件向下传入时定义逻辑); - 在顶层组件
Game
创建系统的属性、方法定义(实现逻辑处理),value
接收整个state
- 在需要使用的子组件中
NameContext.Consumer
渲染时接收传入的参数,定义、传入和接收必须保持一致。
Refs&DOM
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建React 元素。 摘自React文档
使用场景:
- 管理焦点,文本选择或媒体播放
- 触发强制动画
- 集成第三方DOM库
React.createRef()
用于创建Ref
,并通过ref
属性附加到组件上;
this.myRef.current
获得该节点的引用;
// parent component
class Game extends React.Component {
constructor(props){
super(props);
this.state = {
name:"world"
}
// create Ref
this.myRef = React.createRef();
}
handleClick(name){
this.setState({
name:name
});
console.log(this.myRef.current);
}
render(){
return (<NameContext.Provider>
<Board ref={this.myRef} onClick={(name)=>this.handleClick(name)} />
</NameContext.Provider>);
}
}
点击按钮后打印出实例:
函数组件没有实例,不能使用ref
属性
回调Refs
不使用createRef
,传递给ref
属性一个回调函数,函数的参数为组件实例或DOM元素
。
// parent component
class Game extends React.Component {
constructor(props){
super(props);
this.state = {
name:"world"
}
// this.myRef = React.createRef();
this.domRef = null;
this.getDOM=dom=>{this.domRef = dom};
}
handleClick(name){
this.setState({
name:name
});
console.log(this.domRef);
}
render(){
return (<NameContext.Provider>
<Board ref={this.getDOM} onClick={(name)=>this.handleClick(name)} />
</NameContext.Provider>);
}
}
Refs 转发
将ref
自动地通过组件传递到其一子组件。隔层传递;
const Board = React.forwardRef((props,ref)=>(
<button ref={ref} onClick={()=>props.onClick("hello")}>
{props.name}
</button>
));
// parent component
class Game extends React.Component {
constructor(props){
super(props);
this.state = {
name:"world"
}
this.myRef = React.createRef();
//this.domRef = null;
//this.getDOM=dom=>{this.domRef = dom};
}
handleClick(name){
this.setState({
name:name
});
console.log(this.myRef.current);
}
render(){
return (<NameContext.Provider>
<Board ref={this.myRef} name={this.state.name} onClick={(name)=>this.handleClick(name)} />
</NameContext.Provider>);
}
}