学习React中的Context

204 阅读5分钟

应用场景

  • 当你需要在整个组件树传递数据,但是不想手动的在每一层传递属性时。
  • Props提供的是在上下级组件(自上而下一级一级)的数据传递
  • Context提供了在组件之间的数据传递(不需要一级一级传递)

应用案例

改变指定组件的边框颜色

旧版context的用法

  • 导入 prop-types包

    import PropTypes from 'prop-types'
    
  • 在父组件中先定义子上下文的属性和方法的类型,然后再去实现子上下文

    static childContextTypes = {
        color:PropTypes.string,
        setColor:PropTypes.func
    }
    
    getChildContext(){
        return {
            color:this.state.color,
            setColor:this.setColor
        }
    }
    
  • 在子孙组件的获取需要的上下文属性,可以全部获取,可以部分获取

    static contextTypes{
        color: PropTypes.string,
        setColor:PropTypes.func
    }
    
  • 代码实现:

    import React from 'react'
    import PropTypes from 'prop-types'
    
    class Title extends React.Component {
    	constructor(props) {
    		super(props)
    	}
    
    	static contextTypes = {
    		color: PropTypes.string
    	}
    
    	render() {
    		let style = {
    			border: '5px solid ' + this.context.color,
    			padding: '10px'
    		}
    		return <div style={style}>Title</div>
    	}
    }
    
    class Header extends React.Component {
    	constructor(props) {
    		super(props)
    	}
    	static contextTypes = {
    		color: PropTypes.string
    	}
    
    	render() {
    		let style = {
    			border: '5px solid ' + this.context.color,
    			padding: '10px'
    		}
    		return (
    			<div style={style}>
    				Header
    				<Title />
    			</div>
    		)
    	}
    }
    
    export default class Page extends React.Component {
    	constructor(props) {
    		super(props)
    		this.state = {
    			color: 'red'
    		}
    	}
    
    	// 定义子上下文的属性和方法类型
    	static childContextTypes = {
    		color: PropTypes.string,
    		setColor: PropTypes.func
    	}
    
    	// 实现子上下文
    	getChildContext() {
    		return {
    			color: this.state.color,
    			setColor: this.setColor
    		}
    	}
    
    	setColor = (color) => {
    		this.setState({
    			color
    		})
    	}
    
    	render() {
    		let style = {
    			border: '5px solid ' + this.state.color,
    			padding: '10px',
    			height: '270px',
    			width: '200px'
    		}
    		return (
    			<div style={style}>
    				page
    				<Header />
    				<Main />
    			</div>
    		)
    	}
    }
    
    class Content extends React.Component {
    	constructor(props) {
    		super(props)
    	}
    
    	static contextTypes = {
    		color: PropTypes.string,
    		setColor: PropTypes.func
    	}
    
    	render() {
    		let style = {
    			border: '5px solid ' + this.context.color,
    			padding: '10px'
    		}
    		return (
    			<div style={style}>
    				Content
    				<br />
    				<button
    					onClick={() => this.context.setColor('red')}
    					style={{ marginRight: '5px' }}
    				>
    					红色
    				</button>
    				<button onClick={() => this.context.setColor('green')}>
    					绿色
    				</button>
    			</div>
    		)
    	}
    }
    
    class Main extends React.Component {
    	constructor(props) {
    		super(props)
    	}
    
    	static contextTypes = {
    		color: PropTypes.string
    	}
    
    	render() {
    		let style = {
    			border: '5px solid ' + this.context.color,
    			padding: '10px',
    			marginTop: '20px'
    		}
    		return (
    			<div style={style}>
    				Main
    				<Content />
    			</div>
    		)
    	}
    }
    
  • 实现效果

新版context的用法

  • 全局创建一个上下文
let ThemeContext = React.createContext()
  • 在父组件中 render的返回值中 使用 ThemeContext.Provider 将value 传递给子孙组件,属性value(这个名称是固定的)
return (
    <ThemeContex.Provider value={需要传递的对象或者值}>
        原组件内容
    </ThemeContex.Provider>
)
  • 在子组件中的使用

    • 类组件

      定义一个contextType就可以通过this.context 获取到父组件传递过来的对象或者值

      static contextType = ThemeContext
      
    • 函数组件

      使用ThemeContext.Consumer 获取到父组件传递过来的对象或者值

      return (
          <ThemeContext.Consumer>
              {
                  (context)=>{
                  	<div
      			style={{
      				border: '5px solid ' + context.color,
      				padding: '10px'
      			}}
      		>
      			Title Function
      		</div>
                  }
              }
          </ThemeContext.Consumer>
      )
      
  • 代码实现:

import React from 'react'
import PropTypes from 'prop-types'
let ThemeContext = React.createContext()
/* class Title extends React.Component {
    static contextType = ThemeContext
    
    render() {
    	let style = {
    		border: '5px solid ' + this.context.color,
    		padding: '10px'
    	}
    	return <div style={style}>Title</div>
    }
} */
    
function Title() {
    return (
    	<ThemeContext.Consumer>
    		{(context) => (
    			<div
    				style={{
    					border: '5px solid ' + context.color,
    					padding: '10px'
    				}}
    			>
    				Title Function
    			</div>
    		)}
    	</ThemeContext.Consumer>
    )
}
    
class Header extends React.Component {
    static contextType = ThemeContext
    render() {
    	let style = {
    		border: '5px solid ' + this.context.color,
    		padding: '10px'
    	}
    	return (
    		<div style={style}>
    			Header
    			<Title />
    		</div>
    	)
    }
}
    
export default class Page extends React.Component {
    state = {
    	color: 'red'
    }
    setColor = (color) => {
    	this.setState({
    		color
    	})
    }
    render() {
    	let style = {
    		border: '5px solid ' + this.state.color,
    		padding: '10px',
    		height: '270px',
    		width: '200px'
    	}
    	return (
    		<ThemeContext.Provider
    			value={{ color: this.state.color, setColor: this.setColor }}
    		>
    			<div style={style}>
    				page
    				<Header />
    				<Main />
    			</div>
    		</ThemeContext.Provider>
    	)
    }
}
    
    
 class Content extends React.Component {
    static contextType = ThemeContext
    render() {
    	let style = {
    		border: '5px solid ' + this.context.color,
    		padding: '10px'
    	}
    	return (
    		<div style={style}>
    			Content
    			<br />
    			<button
    				onClick={() => this.context.setColor('red')}
    				style={{ marginRight: '5px' }}
    			>
    				红色
    			</button>
    			<button onClick={() => this.context.setColor('green')}>
    				绿色
    			</button>
    		</div>
    	)
    }
}
    
class Main extends React.Component {
    static contextType = ThemeContext
    render() {
    	let style = {
    		border: '5px solid ' + this.context.color,
    		padding: '10px',
    		marginTop: '20px'
    	}
    	return (
    		<div style={style}>
    			Main
    			<Content />
    		</div>
    	)
    }
}

  • 效果图(同旧版一致)

简单实现 新版context

分析新版context

  • React.createReact() 是一个无入参的函数,返回值为一个对象,{Provider,Consumer}

  • Provider

    • 提供者,父组件通过它将数据传递给子孙组件
    • 它是一个类组件,没有自己的react元素,渲染的是子组件,接收一个value(固定名称)的props属性, 这个属性是一个静态属性,可以通过组件获取,
    class Provider extends React.Component{
        static value
        constructor(props){
            super(props)
            // 获取value赋值给静态属性
            Provider.value = props.value
        }
        render(){
            // 直接渲染子组件
            return this.props.children
        }
    }
    
    • 在状态更新的时候,同时更新类组件的静态属性value的值
    // 状态或者组件更新的时候,这个方法执行
    static getDerivedStateFromProps(nextProps,preState){
        // 更新value值
        Provider.value = nextProps.value
    }
    
  • Consumer

    • 子孙组件(函数组件)可以通过它来获取父组件传递过来的数据
    • 也是一个组件
    • 直接渲染子组件,传入父组件传递过来的数据
    render(){
        return this.props.children(Provider.value)
    }
    
  • 注意事项

    • 我们首先要定义一下两个变量,去欺骗react,将我们的context当成自己的
    • 在类组件中获取父组件传递的数据时,通过当前组件的静态属性contextType找到其Provider的属性的value值
    static contextType = ThemeContext
    this.context = Main.contextType.Provider.value
    
  • 代码实现

import React from 'react'
// 定义这两个常量是为了骗过react,将我们定义的context当成自己的
const REACT_CONTEXT_TYPE = Symbol.for('react.context')
const REACT_PROVIDER_TYPE = Symbol.for('react.provider')
let ThemeContext = createContext()

function createContext() {
	class Provider extends React.Component {
		$$typeof = REACT_PROVIDER_TYPE
		static value
		constructor(props) {
			super(props)
			Provider.value = props.value
		
		}
		static getDerivedStateFromProps(nextProps, preState) {
			Provider.value = nextProps.value
		
		}
		render() {
			return this.props.children
		}
	}
	class Consumer extends React.Component {
		render() {
			return this.props.children(Provider.value)
		}
	}
	return { $$typeof: REACT_CONTEXT_TYPE, Provider, Consumer }
}

/* class Title extends React.Component {
	static contextType = ThemeContext

	render() {
		this.context = Title.contextType.Provider.value
		let style = {
			border: '5px solid ' + this.context.color,
			padding: '10px'
		}
		return <div style={style}>Title</div>
	}
} */

function Title() {
	return (
		<ThemeContext.Consumer>
			{(context) => (
				<div
					style={{
						border: '5px solid ' + context.color,
						padding: '10px'
					}}
				>
					Title Function
				</div>
			)}
		</ThemeContext.Consumer>
	)
}

class Header extends React.Component {
	static contextType = ThemeContext
	render() {
		this.context = Header.contextType.Provider.value
		let style = {
			border: '5px solid ' + this.context.color,
			padding: '10px'
		}
		return (
			<div style={style}>
				Header
				<Title />
			</div>
		)
	}
}

export default class Page extends React.Component {
	state = {
		color: 'red'
	}
	setColor = (color) => {
		this.setState({
			color
		})
	}
	render() {
		let style = {
			border: '5px solid ' + this.state.color,
			padding: '10px',
			height: '270px',
			width: '200px'
		}
		return (
			<ThemeContext.Provider
				value={{ color: this.state.color, setColor: this.setColor }}
			>
				<div style={style}>
					page
					<Header />
					<Main />
				</div>
			</ThemeContext.Provider>
		)
	}
}

class Content extends React.Component {
	static contextType = ThemeContext
	render() {
		this.context = Content.contextType.Provider.value
		let style = {
			border: '5px solid ' + this.context.color,
			padding: '10px'
		}
		return (
			<div style={style}>
				Content
				<br />
				<button
					onClick={() => this.context.setColor('red')}
					style={{ marginRight: '5px' }}
				>
					红色
				</button>
				<button onClick={() => this.context.setColor('green')}>
					绿色
				</button>
			</div>
		)
	}
}

class Main extends React.Component {
	static contextType = ThemeContext
	render() {
		this.context = Main.contextType.Provider.value
		let style = {
			border: '5px solid ' + this.context.color,
			padding: '10px',
			marginTop: '20px'
		}
		return (
			<div style={style}>
				Main
				<Content />
			</div>
		)
	}
}

  • 实现效果