//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./components/app.js";
ReactDOM.render(<App />,document.querySelector('#root'));
//app.js
import React from 'react';
import Toolbar from "./toolbar.js";
function App(props){
return <Toolbar theme={
'dark'} />
}
export default App;
//toolbar.js
import React from 'react';
import Button from "./Button/button.js";
function Toolbar(props){
return (
<div>
<Button theme={
props.theme} />
</div>
);
}
export default Toolbar;
//button.js
import React from 'react';
import "./button.css";
function Button(props){
return <button className={
props.theme}>按钮</button>
}
export default Button;
//button.css
.dark{
background-color:darkkhaki;
}
我们知道,props可以将数据从父组件传递给子组件。
现在,顶级组件App
要传递theme
这个数据给底层组件Button
,经历了这么一个传递路径:App
–>Toolbar
–>Button
。
然而,Toolbar
根本用不上theme
,它只是一个中转站。
在组件设计过程中,我们常常尽力弱化组件间的依赖关系,以期望组件之间彼此独立。
这里用组件扮演中转站的角色明显辜负了我们的期望。
如果有个全局变量就好了,诶,React
给我们提供了类似于全局变量的东西来实现数据共享,那就是Context。
//新增context.js,输出context对象
import React from "react";
export default React.createContext('light');
//修改app.js
import React from 'react';
import Toolbar from "./toolbar.js";
import Context from "./context.js";
function App(props){
return (
<Context.Provider value='dark'>
<Toolbar/>
</Context.Provider>
);
}
export default App;
//修改toolbar.js
import React from 'react';
import Button from "./Button/button.js";
function Toolbar(){
return (
<div>
<Button/>
</div>
);
}
export default Toolbar;
//修改button.js
import React from 'react';
import "./button.css";
import Context from "../context.js";
class Button extends React.Component{
static contextType = Context;
render(){
return <button className={
this.context}>按钮</button>
}
}
//Button.contextType = Context;
export default Button;
使用static contextType = Context
可能遇到问题:Support for the experimental syntax ‘classProperties’ isn’t currently enabled。
安装插件@babel/plugin-proposal-class-propertie
,并将其添加至webpack配置文件里可解决。
{
test:/\.js$/,
include:/src/,
exclude:/node_modules/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-react'],
plugins:["@babel/plugin-proposal-class-properties"]
}
}
}
redux里,组件间共享store就是用Context实现的。
好了,继续了解下Context。
React.createContext("light")
, 创建一个Context对象且该对象的_currentValue属性初始化为"light",即context._currentValue="light"
。
是一个组件,接受value
属性来更新 context._currentValue
。
,所以context._currentValue="dark"
。Button.contextType=Context
,即Button类的contextType
属性是一个Context对象。contextType
属性,且该属性是一个对象,于是获取其_currentValue
值并赋给Button实例的context
属性,即this.context=context._currentValue
。this.context
的值就是"dark"。
也是一个组件,消费组件,就是用到了Context的组件。
的子组件必须是一个函数,该函数接受一个参数,该参数值就是this.context
会从组件树中离自己最近的
中获取this.context
。this.context
值,这个Text组件就是消费组件。现在,在App组件中引入Text组件并放于两个不同的位置。import React from "react";
import Context from "./context.js";
function Text(){
return (
<Context.Consumer>
{
value => <span>{
value}</span>
}
</Context.Consumer>
);
}
export default Text;
import React from 'react';
import Toolbar from "./toolbar.js";
import Context from "./context.js";
import Text from "./text.js";
function App(props){
return (
<>
<Context.Provider value='dark'>
<Text/>
<Toolbar/>
</Context.Provider>
<Text/>
</>
);
}
export default App;
瞧,两个 Text
组件取到的this.context
值是不同的。
context
实现数据共享,相关应用还可以看下 购物车实例。
使用this.state
同步记录this.context
,点击按钮可更新this.state
,从而改变this.context
。
//button.css
.dark{
background-color:darkkhaki;
}
.light{
background-color:lightyellow;
}
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./components/app.js";
ReactDOM.render(<App/>,document.querySelector('#root'));
//app.js
import React from 'react';
import Toolbar from "./toolbar.js";
import Context from "./context.js";
class App extends React.Component{
constructor(props){
super(props);
this.state = {
theme:"dark"
}
this.onChangeTheme = this.onChangeTheme.bind(this);
}
onChangeTheme(){
this.setState(state => ({
theme:state.theme==="light"?"dark":"light"
}));
// this.setState(state => {
// return {
// theme:state.theme==="light"?"dark":"light"
// }
// })
}
render(){
return (
<Context.Provider value={
this.state.theme}>
<Toolbar changeTheme={
this.onChangeTheme}/>
</Context.Provider>
);
}
}
export default App;
//toolbar.js
import React from 'react';
import Button from "./Button/button.js";
function Toolbar(props){
return (
<div>
<Button onClick={
props.changeTheme}/>
</div>
);
}
export default Toolbar;
//button.js
import React from 'react';
import "./button.css";
import Context from "../context.js";
class Button extends React.Component{
render(){
return <button className={
this.context}
// onClick={this.props.onClick}
{
...this.props}
>按钮</button>
}
// render(){
// return (
//
// {value => }
//
// )
// }
}
Button.contextType = Context;
export default Button;
//context.js
import React from "react";
export default React.createContext('light');
{...this.props}
或者{...props}
看下面这个简单的例子来理解下。
import React from 'react';
function Greeting(props){
const {
firstname,lastname} = props;
return <span>Welcome,{
firstname} {
lastname}</span>;
}
function App(){
return <Greeting firstname="Steven" lastname="Jobs"/>;
}
export default App;
import React from 'react';
function Greeting(props){
const {
firstname,lastname} = props;
return <span>Welcome,{
firstname} {
lastname}</span>;
}
function App(){
const obj = {
firstname:"Steven",
lastname:"Jobs"
}
return <Greeting {
...obj} />;
}
export default App;
()
括起来即可 this.setState(state => ({
theme:state.theme==="light"?"dark":"light"
}));
// this.setState(state => {
// return {
// theme:state.theme==="light"?"dark":"light"
// }
// })
只接受函数为 子组件//以函数为子组件,OK
<Context.Consumer>
{
value => <button className={
value} {
...this.props}>按钮</button>}
</Context.Consumer>
//子组件不是函数,NOK
//A context consumer expects a single child that is a function
<Context.Consumer>
<button className={
this.context} {
...this.props}>按钮</button>
</Context.Consumer>
使用Context传递函数,解决 组件嵌套太深 难以更新this.context
的问题。
和 demo1 一样,使用 this.state
同步记录this.context
。
不同的是,这里的this.context
是 一个对象。
//context.js
import React from "react";
export default React.createContext({
theme:"light",
changeTheme: () => {
}
});
//app.js
import React from "react";
import Context from "./context.js";
import Button from "./Button/button.js";
class App extends React.Component{
constructor(props){
super(props);
this.onChangeTheme = this.onChangeTheme.bind(this);
this.state = {
theme:"light",
changeTheme:this.onChangeTheme
}
}
onChangeTheme(){
this.setState(state => ({
theme:state.theme==="light"?"dark":"light"
}));
}
render(){
return (
<Context.Provider value={
this.state}>
<Button />
</Context.Provider>
)
}
}
export default App;
//button.js
import React from "react";
import Context from "../context.js";
import "./button.css";
class Button extends React.Component{
render(){
return (
<Context.Consumer>
{
({
theme,changeTheme}) => <button className={
theme} onClick={
changeTheme}>按钮</button>
}
</Context.Consumer>
)
}
}
Button.contextType = Context;
export default Button;
不用Context,用props传递组件 也可以解决中转站的问题。
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./components/app.js";
ReactDOM.render(<App theme='dark' />,document.querySelector('#root'));
//app.js
import React from 'react';
import Button from "./Button/button.js";
import Toolbar from "./toolbar.js";
function App(props){
const button = <Button theme={
props.theme}/>
return (
<Toolbar button={
button}>
</Toolbar>
);
}
export default App;
//toolbar.js
import React from 'react';
function Toolbar(props){
return (
<div>
{
props.button}
</div>
);
}
export default Toolbar;
//button.js
import React from 'react';
import "./button.css";
function Button(props){
return <button className={
props.theme}>按钮</button>
}
export default Button;
既然顶层组件App
和底层组件Button
都用到了theme
,那它俩自己商量着解决就好了,没有必要将根本用不着theme
的Toolbar
也牵扯进来。于是,在App
中将Button
组合进来,并将Button
这个组件通过props
传递下去,这样其它组件就不用关心theme
这个数据了。