使用props作为属性数据共享的载体,在组件之间传输;但是在react中这种传输时单向的,即父组件->子组件,想要将子组件中的数据变动让其他组件也接收到,需要做处理。
2019-7-29 更新
完善Context的嵌套属性传入解决示例;完善Context动态更新属性解决示例;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 提供了一个无需为每层组件手动添加 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 节点或在 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属性
不使用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>); } }将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>); } }