React学习之1.数据传递Context

数据传递

React 的特性单向数据流,限制了正常情况下,数据只能自上而下传递(父组件 > 子组件)。
可以通过其他方式打破这种限制:

  1. 组件组合(props.children上绑定数据):省略了嵌套层级太多的组件层层传递数据。类似vue插槽(slot)
  2. 事件绑定:通过props将触发事件执行的函数绑定到子组件,实现子组件向上通讯。
  3. Context:创建context,使整个组件树共享数据。
  4. 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设置默认值,不需要读取Context

export 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; // 改为指向ProductsContext
    render() {
        // 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的使用

  1. contextType只对一个Context有效,如果一个组件只使用一个context,请使用contextType,如果一个组件需要读取多个context,那就需要使用Consumer。
  2. contextType简化了Consumer的使用,直接赋值即可。
  3. contextType可以在组件任意地方读取context,Consumber只能在<Consumber>的子元素函数中使用。

Context应用场景

Context依赖了类似“全局”的变量,来共享那些对一个组件树而言是“全局”的数据。
会使组件的复用性变差,应谨慎使用。
主要应用场景在于很多不同层级的组件需要访问一些同样的数据。

但是Context会让组件变得不纯粹,因为依赖了全局变量。所以这决定了Context一般不会大规模的使用。所以一般在一个组件中使用一个Context就好。
例如当前的locale,theme,一些缓存数据,或一些全局的公共数据。

如果只是像避免方便的进行数据传递,React更推荐使用组件组合来解决。

Redux:依据context原理和Flux架构,在最顶层通过一个集中的Data Store管理公共数据和状态变更,类似vuex。

参考

Context | React文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React 中,可以通过 Context API 来进行全局的数据传递,i18n 配置也可以通过 Context API 进行传递。 1. 首先,创建一个 i18n 配置对象,例如: ``` const i18nConfig = { locale: 'zh-CN', messages: { 'hello': '你好', 'bye': '再见' } }; ``` 这里的 `locale` 属性表示当前的语言环境,`messages` 属性则是一个键值对,用于存储不同语言环境下的文本信息。 2. 然后,创建一个 Context 对象,例如: ``` const I18nContext = React.createContext(i18nConfig); ``` 这里的 `I18nContext` 就是我们定义的 Context 对象,它的初始值是我们之前创建的 i18n 配置对象。 3. 在组件中使用 Context 对象,例如: ``` function MyComponent() { const i18n = useContext(I18nContext); return ( <div> <p>{i18n.messages['hello']}</p> <p>{i18n.messages['bye']}</p> </div> ); } ``` 这里的 `useContext` 是 React 提供的一个 Hook,用于从 Context 对象中获取数据。在这个例子中,我们从 `I18nContext` 中获取到 i18n 配置对象,然后根据当前语言环境显示不同的文本信息。 4. 在根组件中设置 Context 对象的值,例如: ``` function App() { const i18nConfig = { locale: 'en-US', messages: { 'hello': 'Hello', 'bye': 'Goodbye' } }; return ( <I18nContext.Provider value={i18nConfig}> <MyComponent /> </I18nContext.Provider> ); } ``` 这里的 `App` 组件是根组件,我们在其中设置 `I18nContext` 的值为英文环境。这样,在整个应用中,所有使用了 `I18nContext` 的组件都将会使用英文环境的文本信息。 通过以上步骤,我们就可以使用 ReactContext API 来进行全局的 i18n 配置传递了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值