本文主要是介绍React学习之1.数据传递Context,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
数据传递
React 的特性单向数据流,限制了正常情况下,数据只能自上而下传递(父组件 > 子组件)。
可以通过其他方式打破这种限制:
- 组件组合(props.children上绑定数据):省略了嵌套层级太多的组件层层传递数据。类似vue插槽(slot)
- 事件绑定:通过props将触发事件执行的函数绑定到子组件,实现子组件向上通讯。
- Context:创建context,使整个组件树共享数据。
- Redux:使用React的Context原理的工具。
静态页面配置
本次学习Context,创建一个简单的可以切换主题样式的列表查询功能。
// index.css
*{margin:0;padding:0;box-sizing: border-box;}
html,body{font-size:14px;}#root{width:500px;margin: 50px auto;
}
.theme{margin-bottom:10px;
}
.theme label{margin-right:20px;
}
.dark .theme{color:#fff;
}
#searchBox{background-color: #eee;padding: 10px;
}
#searchBox.dark{background-color: #333;
}
.query{margin-bottom: 10px;
}
.query input[type=text]{height: 36px;line-height: 36px;font-size: 14px;padding:0 5px;border: 1px solid #999;
}
.query select{margin-left: 10px;height: 36px;line-height: 36px;padding:0 5px;border: 1px solid #999;
}.query.dark input[type=text],.query.dark select{border-color:#eee;background-color: #777;color:#ccc;
}.table{background-color: #fff;border: solid #999;border-width: 1px 1px 0 0;width: 100%;border-spacing: 0;
}
.table td,.table th{border: solid #999;border-width: 0 0 1px 1px;padding: 4px;
}
.table.dark,.table.dark td,.table.dark th{border-color:#eee;background-color: #777;color:#ccc;
}
// SearchApp.js
import React from 'react';// table行组件
class TableRow extends React.Component {render() {return <tr><td>category</td><td>name</td><td>price</td></tr>;}
}// table组件
class Table extends React.Component {render() {let rows = [];return (<table className={'table '}><thead><tr><th>Category</th><th>Name</th><th>Price</th></tr></thead><tbody>{rows}</tbody></table>);}
}// 查询项组件
class SearchBar extends React.Component {render() {return (<div className={'query '}><input type="text" /><select><option value="">All</option><option value="Sporting Goods">Sporting Goods</option><option value="Electronics">Electronics</option></select></div>);}
}// 查询功能组件
class SearchBox extends React.Component {render() {return (<div id="searchBox"><SearchBar /><Table /></div>);}
}// 功能容器组件
class SearchApp extends React.Component {render() {return (<SearchBox />);}
}export default SearchApp;
Context 简单使用
创建context对象
const ContextName = React.createContext(defaultValue);
每个Context对象都返回两个React组件:Provider和Consumer
Provider:允许消费组件(被包裹的组件树)订阅Context的变更。
Consumer:在子孙级组件使用,允许消费组件订阅Context变更。子元素是一个函数。用于一个组件消费多个Context 或 传递函数变更Context的默认值(实际上是变更Provider组件的value属性,不是变更Context对象的defaultValue)
读取context
指定组件的contextType属性(只能指向一个Context对象),就可以在组件内甚至render中使用this.context读取Context的值。
import React from 'react';// 创建一个context对象,存储主题相关数据,默认值light
const ThemeContext = React.createContext('light');
// 使用组件<ThemeContext.Provider> <ThemeContext.Consumer>// 或如此声明:const {Provider,Consumer} = ThemeContext;
// 使用组件<Provider> <Consumer>// table行组件
class TableRow extends React.Component {render() {return <tr><td>category</td><td>name</td><td>price</td></tr>;}
}// table组件
class Table extends React.Component {render() {const theme = this.context;let rows = [];return (<table className={'table ' + theme}><thead><tr><th>Category</th><th>Name</th><th>Price</th></tr></thead><tbody>{rows}</tbody></table>);}
}
// 指定组件的contextType
Table.contextType = ThemeContext;// 查询项组件
class SearchBar extends React.Component {// 指定contextType的新写法static contextType = ThemeContext;render() {const theme = this.context;return (<div className={'query ' + theme}><input type="text" /><select><option value="">All</option><option value="Sporting Goods">Sporting Goods</option><option value="Electronics">Electronics</option></select></div>);}
}// 查询功能组件
class SearchBox extends React.Component {render() {return (<div id="searchBox" className={this.context}><SearchBar /><Table /></div>);}
}
SearchBox.contextType = ThemeContext;// 功能容器组件
class SearchApp extends React.Component {render() {return (<ThemeContext.Provider value="dark"><div>SearchApp组件获取到的ThemeContext值是:{this.context}</div><SearchBox /></ThemeContext.Provider>);}
}
SearchApp.contextType = ThemeContext;export default SearchApp;
当 React 渲染一个订阅了 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
所以上面SearchApp组件读取的context值是 light,而不是它下级组件配置的默认值 dark。
动态修改context
将context的默认值指向组件的state中的数据,并将一个可以变更这个数据的方法传递给context。可以使组件树中的组件动态的修改context。
Provider传递函数
import React from 'react';// 默认值将被组件的state替换,可以不设置
const ThemeContext = React.createContext();
// 或设置一个默认值,避免意外
// const ThemeContext = React.createContext({
// theme: 'light',
// toggleTheme: () => {}
// });// table行组件
class TableRow extends React.Component {render() {return <tr><td>category</td><td>name</td><td>price</td></tr>;}
}// table组件
class Table extends React.Component {render() {const {theme} = this.context;let rows = [];return (<table className={'table ' + theme}><thead><tr><th>Category</th><th>Name</th><th>Price</th></tr></thead><tbody>{rows}</tbody></table>);}
}
// 指定组件的contextType
Table.contextType = ThemeContext;// 查询项组件
class SearchBar extends React.Component {// 指定contextType的新写法static contextType = ThemeContext;render() {const {theme} = this.context;return (<div className={'query ' + theme}><input type="text" /><select><option value="">All</option><option value="Sporting Goods">Sporting Goods</option><option value="Electronics">Electronics</option></select></div>);}
}// 查询功能组件
class SearchBox extends React.Component {render() {const {theme,toggleTheme} = this.context;return (<div id="searchBox" className={theme}><div className="theme"><label><input type="radio" name="theme" value="light" checked={theme==='light'} onChange={(e) => {toggleTheme(e.target.value)}} /> light</label><label><input type="radio" name="theme" value="dark" checked={theme==='dark'} onChange={(e) => {toggleTheme(e.target.value)}} /> dark</label></div><SearchBar /><Table /></div>);}
}
SearchBox.contextType = ThemeContext;// 功能容器组件
class SearchApp extends React.Component {constructor(props) {super(props);this.toggleTheme = this.toggleTheme.bind(this);this.state = {theme: 'dark',toggleTheme: this.toggleTheme}}// 变更主题toggleTheme(val) {this.setState({theme: val})}render() {const {theme,toggleTheme} = this.state;return (// 或直接指向this.state<ThemeContext.Provider value={{theme,toggleTheme}}><SearchBox /></ThemeContext.Provider>);}
}
// SearchApp.contextType = ThemeContext; // 使用自己的state设置默认值,不需要读取Contextexport default SearchApp;
Consumer传递函数
在子孙级组件使用Consumer,可以通过函数传递context的值,而不需要指定组件的contextType。
修改SearchBox组件,使功能与上例一样:
// 查询功能组件
class SearchBox extends React.Component {render() {// const {theme,toggleTheme} = this.context; // 未指定contextType,this.context此时为{}return (<ThemeContext.Consumer>{({theme, toggleTheme}) => {return (<div id="searchBox" className={theme}><div className="theme"><label><input type="radio" name="theme" value="light" checked={theme==='light'} onChange={(e) => {toggleTheme(e.target.value)}} /> light</label><label><input type="radio" name="theme" value="dark" checked={theme==='dark'} onChange={(e) => {toggleTheme(e.target.value)}} /> dark</label></div><SearchBar /><Table /></div>);}}</ThemeContext.Consumer>);}
}
// SearchBox.contextType = ThemeContext; // 不用指定contextType
使用多个Context
嵌套Provider
创建一个Context,用于共享产品列表数据和筛选方法。
通过嵌套Provider组件使用多个Context。
import React from 'react';const ThemeContext = React.createContext();
const ProductsContext = React.createContext();// table行组件
class TableRow extends React.Component {render() {return <tr><td>{this.props.category}</td><td>{this.props.name}</td><td>{this.props.price}</td></tr>;}
}// table组件
class Table extends React.Component {render() {// const {theme} = this.context;const theme = 'dark'; // contextType只能指定一个Context对象const {products} = this.context;let rows = products.map((v,i)=><TableRow {...v} key={i}></TableRow>);return (<table className={'table ' + theme}><thead><tr><th>Category</th><th>Name</th><th>Price</th></tr></thead><tbody>{rows}</tbody></table>);}
}
// Table.contextType = ThemeContext;
Table.contextType = ProductsContext; // 改为指向ProductsContext// 查询项组件
class SearchBar extends React.Component {// static contextType = ThemeContext;static contextType = ProductsContext; // 改为指向ProductsContextrender() {// const {theme} = this.context;const theme = 'dark'; // contextType只能指定一个Context对象const {searchList,keyword,category} = this.context;return (<div className={'query ' + theme}><input type="text" name="keyword" value={keyword} onChange={searchList} /><select name="category" value={category} onChange={searchList} ><option value="">All</option><option value="Sporting Goods">Sporting Goods</option><option value="Electronics">Electronics</option></select></div>);}
}// 查询功能组件
class SearchBox extends React.Component {render() {return (<ThemeContext.Consumer>{({theme, toggleTheme}) => (<div id="searchBox" className={theme}><div className="theme"><label><input type="radio" name="theme" value="light" checked={theme==='light'} onChange={(e) => {toggleTheme(e.target.value)}} /> light</label><label><input type="radio" name="theme" value="dark" checked={theme==='dark'} onChange={(e) => {toggleTheme(e.target.value)}} /> dark</label></div><SearchBar /><Table /></div>)}</ThemeContext.Consumer>);}
}// 功能容器组件
class SearchApp extends React.Component {constructor(props) {super(props);this.toggleTheme = this.toggleTheme.bind(this);this.searchList = this.searchList.bind(this);this.products = [{ category: "Sporting Goods", price: "$49.99", name: "Football" },{ category: "Sporting Goods", price: "$9.99", name: "Baseball" },{ category: "Sporting Goods", price: "$29.99", name: "Basketball" },{ category: "Electronics", price: "$99.99", name: "iPod Touch" },{ category: "Electronics", price: "$399.99", name: "iPhone 5" },{ category: "Electronics", price: "$199.99", name: "Nexus 7" }];this.state = {theme: 'dark',toggleTheme: this.toggleTheme,products: this.products,searchList: this.searchList,keyword: '',category: ''}}// 变更主题toggleTheme(val) {this.setState({theme: val})}// 筛选产品列表searchList(event) {let {keyword,category} = this.state;let products = this.products;let value = event.target.value;if (event.target.name === 'category') {category = value;}if (event.target.name === 'keyword') {keyword = value;}if (category) {products = products.filter(v => {return v.category === category;});}if (keyword) {products = products.filter(v => {return v.name.indexOf(keyword) !== -1;});}this.setState({products,[event.target.name]: value})}render() {const {theme,toggleTheme,products,searchList,keyword,category} = this.state;return (<ProductsContext.Provider value={{products,searchList,keyword,category}}><ThemeContext.Provider value={{theme,toggleTheme}}><SearchBox /></ThemeContext.Provider></ProductsContext.Provider>);}
}export default SearchApp;
嵌套Consumer
虽然实现了传递多个Context,但是使用contextType只能指向一个Context对象,所以这种方式不能读取所有的Context。
这时就是使用Consumer的时候了,同样也是嵌套,修改代码:
import React from 'react';const ThemeContext = React.createContext();
const ProductsContext = React.createContext();// table行组件
class TableRow extends React.Component {render() {return <tr><td>{this.props.category}</td><td>{this.props.name}</td><td>{this.props.price}</td></tr>;}
}// table组件
class Table extends React.Component {render() {return (<ThemeContext.Consumer>{({theme}) => (<ProductsContext.Consumer>{({products}) => (<table className={'table ' + theme}><thead><tr><th>Category</th><th>Name</th><th>Price</th></tr></thead><tbody>{products.map((v,i)=><TableRow {...v} key={i}></TableRow>)}</tbody></table>)}</ProductsContext.Consumer>)}</ThemeContext.Consumer>);}
}
// Table.contextType = ThemeContext;
Table.contextType = ProductsContext; // 改为指向ProductsContext// 查询项组件
class SearchBar extends React.Component {render() {return (<ThemeContext.Consumer>{({theme}) => (<ProductsContext.Consumer>{({searchList,keyword,category}) => (<div className={'query ' + theme}><input type="text" name="keyword" value={keyword} onChange={searchList} /><select name="category" value={category} onChange={searchList} ><option value="">All</option><option value="Sporting Goods">Sporting Goods</option><option value="Electronics">Electronics</option></select></div>)}</ProductsContext.Consumer>)}</ThemeContext.Consumer>);}
}// 查询功能组件
class SearchBox extends React.Component {render() {return (<ThemeContext.Consumer>{({theme, toggleTheme}) => (<div id="searchBox" className={theme}><div className="theme"><label><input type="radio" name="theme" value="light" checked={theme==='light'} onChange={(e) => {toggleTheme(e.target.value)}} /> light</label><label><input type="radio" name="theme" value="dark" checked={theme==='dark'} onChange={(e) => {toggleTheme(e.target.value)}} /> dark</label></div><SearchBar /><Table /></div>)}</ThemeContext.Consumer>);}
}// 功能容器组件
class SearchApp extends React.Component {constructor(props) {super(props);this.toggleTheme = this.toggleTheme.bind(this);this.searchList = this.searchList.bind(this);this.products = [{ category: "Sporting Goods", price: "$49.99", name: "Football" },{ category: "Sporting Goods", price: "$9.99", name: "Baseball" },{ category: "Sporting Goods", price: "$29.99", name: "Basketball" },{ category: "Electronics", price: "$99.99", name: "iPod Touch" },{ category: "Electronics", price: "$399.99", name: "iPhone 5" },{ category: "Electronics", price: "$199.99", name: "Nexus 7" }];this.state = {theme: 'dark',toggleTheme: this.toggleTheme,products: this.products,searchList: this.searchList,keyword: '',category: ''}}// 变更主题toggleTheme(val) {this.setState({theme: val})}// 筛选产品列表searchList(event) {let {keyword,category} = this.state;let products = this.products;let value = event.target.value;if (event.target.name === 'category') {category = value;}if (event.target.name === 'keyword') {keyword = value;}if (category) {products = products.filter(v => {return v.category === category;});}if (keyword) {products = products.filter(v => {return v.name.indexOf(keyword) !== -1;});}this.setState({products,[event.target.name]: value})}render() {const {theme,toggleTheme,products,searchList,keyword,category} = this.state;return (<ProductsContext.Provider value={{products,searchList,keyword,category}}><ThemeContext.Provider value={{theme,toggleTheme}}><SearchBox /></ThemeContext.Provider></ProductsContext.Provider>);}
}export default SearchApp;
contextType和Consumer的使用
- contextType只对一个Context有效,如果一个组件只使用一个context,请使用contextType,如果一个组件需要读取多个context,那就需要使用Consumer。
- contextType简化了Consumer的使用,直接赋值即可。
- contextType可以在组件任意地方读取context,Consumber只能在<Consumber>的子元素函数中使用。
Context应用场景
Context依赖了类似“全局”的变量,来共享那些对一个组件树而言是“全局”的数据。
会使组件的复用性变差,应谨慎使用。
主要应用场景在于很多不同层级的组件需要访问一些同样的数据。
但是Context会让组件变得不纯粹,因为依赖了全局变量。所以这决定了Context一般不会大规模的使用。所以一般在一个组件中使用一个Context就好。
例如当前的locale,theme,一些缓存数据,或一些全局的公共数据。
如果只是像避免方便的进行数据传递,React更推荐使用组件组合来解决。
Redux:依据context原理和Flux架构,在最顶层通过一个集中的Data Store管理公共数据和状态变更,类似vuex。
参考
Context | React文档
这篇关于React学习之1.数据传递Context的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!