├── README.md 文档 ├── public 静态资源 │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src 源码 ├── App.css ├── App.js 根组件 ├── App.test.js ├── index.css 全局样式 ├── index.js 入口文件 ├── logo.svg └── serviceWorker.js pwa支持 ├── package.json npm 依赖
使用 npm run eject 弹出项目真面目,会多出两个目录:javascript
├── config ├── env.js 处理.env环境变量配置文件 ├── paths.js 提供各类路径 ├── webpack.config.js webpack配置文件 └── webpackDevServer.config.js 测试服务器配置文件 └── scripts 启动、打包和测试脚本 ├── build.js 打包脚本、 ├── start.js 启动脚本 └── test.js 测试脚本
env.js用来处理.env文件中配置的环境变量css
// node运行环境:development、production、test等 const NODE_ENV = process.env.NODE_ENV; // 要扫描的文件名数组 var dotenvFiles = [ `${paths.dotenv}.${NODE_ENV}.local`, // .env.development.local `${paths.dotenv}.${NODE_ENV}`, // .env.development NODE_ENV !== 'test' && `${paths.dotenv}.local`, // .env.local paths.dotenv, // .env ].filter(Boolean); // 从.env*文件加载环境变量 dotenvFiles.forEach(dotenvFile => { if (fs.existsSync(dotenvFile)) { require('dotenv-expand')( require('dotenv').config({ path: dotenvFile, }) ); } });
实践一下,修改一下默认端口号,建立.env文件html
// .env PORT=8080
webpack.config.js 是webpack配置文件,开头的常量声明能够看出cra可以支持ts、sass及css模块化vue
// Check if TypeScript is setup const useTypeScript = fs.existsSync(paths.appTsConfig); // style files regexes const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; const sassRegex = /\.(scss|sass)$/; const sassModuleRegex = /\.module\.(scss|sass)$/;
删除src下面全部代码,新建index.jsjava
import React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render(<h1>React666</h1>, document.querySelector('#root'));
React负责逻辑控制,数据 -> VDOM
ReactDom渲染实际DOM,VDOM -> DOM,若是换到移动端,就用别的库来渲染
React使用JSX来描述UI
入口文件定义,webpack.config.jsnode
entry: [ // WebpackDevServer客户端,它实现开发时热更新功能 isEnvDevelopment && require.resolve('react-dev-utils/webpackHotDevClient'), // 应用程序入口:src/index paths.appIndexJs, ].filter(Boolean),
表达式{}的使用,index.js
const name = "react study"; const jsx = <h2>{name}</h2>;
函数也是合法表达式,index.jsreact
const user = { firstName: "tom", lastName: "jerry" }; function formatName(user) { return user.firstName + " " + user.lastName; } const jsx = <h2>{formatName(user)}</h2>;
jsx是js对象,也是合法表达式,index.jswebpack
const greet = <p>hello, Jerry</p> const jsx = <h2>{greet}</h2>;
条件语句能够基于上面结论实现,index.jsgit
const showTitle = true; const title = name ? <h2>{name}</h2> : null; const jsx = ( <div> {/* 条件语句 */} {title} </div> );
数组会被做为一组子元素对待,数组中存放一组jsx可用于显示列表数据github
const arr = [1,2,3].map(num => <li key={num}>{num}</li>) const jsx = ( <div> {/* 数组 */} <ul>{arr}</ul> </div> );
属性的使用
import logo from "./logo.svg"; const jsx = ( <div> {/* 属性:静态值用双引号,动态值用花括号;class、for等要特殊处理。 */} <img src={logo} style={{ width: 100 }} className="img" /> </div> );
css模块化,建立index.module.css,index.js
import style from "./index.module.css"; <img className={style.img} />
class组件
class组件一般拥有状态和生命周期,继承于Component,实现render方法
components/JsxTest.js
import React, { Component } from "react"; import logo from "../logo.svg"; import style from "../index.module.css"; export default class JsxTest extends Component { render() { const name = "react study"; const user = { firstName: "tom", lastName: "jerry" }; function formatName(user) { return user.firstName + " " + user.lastName; } const greet = <p>hello, Jerry</p>; const arr = [1, 2, 3].map(num => <li key={num}>{num}</li>); return ( <div> {/* 条件语句 */} {name ? <h2>{name}</h2> : null} {/* 函数也是表达式 */} {formatName(user)} {/* jsx也是表达式 */} {greet} {/* 数组 */} <ul>{arr}</ul> {/* 属性 */} <img src={logo} className={style.img} alt="" /> </div> ); } }
函数组件一般无状态,仅关注内容展现,返回渲染结果便可。
app.js
import React from "react"; import JsxTest from "./components/JsxTest"; function App() { return ( <div> <JsxTest /> </div> ); } export default App;
components/StateMgt.js
import React, { Component } from "react"; export default function StateMgt { return ( <div> <Clock /> </div> ); }
建立一个Clock组件
class Clock extends React.Component { constructor(props) { super(props); // 使用state属性维护状态,在构造函数中初始化状态 this.state = { date: new Date() }; } componentDidMount() { // 组件挂载时启动定时器每秒更新状态 this.timerID = setInterval(() => { // 使用setState方法更新状态 this.setState({ date: new Date() }); }, 1000); } componentWillUnmount() { // 组件卸载时中止定时器 clearInterval(this.timerID); } render() { return <div>{this.state.date.toLocaleTimeString()}</div>; } }
拓展:setState特性讨论
this.state.counter += 1; //错误的
componentDidMount() { // 假如couter初始值为0,执行三次之后其结果是多少? this.setState({counter: this.state.counter + 1}); this.setState({counter: this.state.counter + 1}); this.setState({counter: this.state.counter + 1}); }
this.setState((state, props) => ({ counter: state.counter + 1}));// 1 this.setState((state, props) => ({ counter: state.counter + 1}));// 2 this.setState((state, props) => ({ counter: state.counter + 1}));// 3
2. 使用定时器:
setTimeout(() => { console.log(this.state.counter); }, 0);
componentDidMount(){ document.body.addEventListener('click', this.changeValue, false) } changeValue = () => { this.setState({counter: this.state.counter+1}) console.log(this.state.counter) }
函数组件中的状态管理
import { useState, useEffect } from "react"; function ClockFunc() { // useState建立一个状态和修改该状态的函数 const [date, setDate] = useState(new Date()); // useEffect编写反作用代码(简单理解就是异步代码) useEffect(() => { // 启动定时器是咱们的反作用代码 const timerID = setInterval(() => { setDate(new Date()); }, 1000); // 返回清理函数 return () => clearInterval(timerID); // 至关于 组件卸载 }, []);// 参数2传递空数组使咱们参数1函数仅执行一次 return <div>{date.toLocaleTimeString()}</div>; }
React中使用onXX写法来监听事件。
范例:用户输入事件,建立EventHandle.js
import React, { Component } from "react"; export default class EventHandle extends Component { constructor(props) { super(props); this.state = { name: "" }; this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.setState({ name: e.target.value }); } render() { return ( <div> {/* 使用箭头函数,不须要指定回调函数this,且便于传递参数 */} {/* <input type="text" value={this.state.name} onChange={e => this.handleChange(e)} /> */} {/* 直接指定回调函数,须要指定其this指向,或者将回调设置为箭头函数属性 */} <input type="text" value={this.state.name} onChange={this.handleChange} /> <p>{this.state.name}</p> </div> ); } }
Props属性传递可用于父子组件相互通讯
// index.js ReactDOM.render(<App title="开课吧真不错" />, document.querySelector('#root')); // App.js <h2>{this.props.title}</h2>
若是父组件传递的是函数,则能够把子组件信息传入父组件,这个常称为状态提高,StateMgt.js
// StateMgt <Clock change={this.onChange}/> // Clock this.timerID = setInterval(() => { this.setState({ date: new Date() }, ()=>{ // 每次状态更新就通知父组件 this.props.change(this.state.date); }); }, 1000);
跨层级组件之间通讯(与vue provide inject相似)
相似vuex,无明显关系的组件间通讯
React V16.3以前的生命周期
范例:验证生命周期,建立Lifecycle.js
import React, { Component } from "react"; export default class Lifecycle extends Component { constructor(props) { super(props); // 经常使用于初始化状态 console.log("1.组件构造函数执行"); } componentWillMount() { // 此时能够访问状态和属性,可进行api调用等 console.log("2.组件将要挂载"); } componentDidMount() { // 组件已挂载,可进行状态更新操做 console.log("3.组件已挂载"); } componentWillReceiveProps(nextProps, nextState) { // 父组件传递的属性有变化,作相应响应 console.log("4.将要接收属性传递"); } shouldComponentUpdate(nextProps, nextState) { // 组件是否须要更新,须要返回布尔值结果,优化点 console.log("5.组件是否须要更新?"); return true; } componentWillUpdate() { // 组件将要更新,可作更新统计 console.log("6.组件将要更新"); } componentDidUpdate() { // 组件更新 console.log("7.组件已更新"); } componentWillUnmount() { // 组件将要卸载, 可作清理工做 console.log("8.组件将要卸载"); } render() { console.log("组件渲染"); return <div>生命周期探究</div>; } }
激活更新阶段:App.js
class App extends Component { state = { prop: "some content" }; componentDidMount() { this.setState({ prop: "new content" }); } render() { return ( <div> <Lifecycle prop={this.state.prop} /> </div> ); } }
激活卸载阶段,App.js
class App extends Component { state = { prop: "some content" }; componentDidMount() { this.setState({ prop: "new content" }); setTimeout(() => { this.setState({ prop: "" }); }, 2000); } render() { return ( <div> {this.state.prop && <Lifecycle prop={this.state.prop} />} </div> ); } }
大部分团队不见得会跟进升到16版本,因此16前的生命周期仍是颇有必要掌握的,况且16也是基于以前的修改
第一个是组件初始化(initialization)阶段
也就是如下代码中类的构造方法( constructor() ),Test类继承了react Component这个基类,也就继承这个react的基类,才能有render(),生命周期等方法可使用,这也说明为何 函数组件不能使用这些方法 的缘由。
super(props) 用来调用基类的构造方法( constructor() ), 也将父组件的props注入给子组件,功子组件读取(组件中props只读不可变,state可变)。 而 constructor() 用来作一些组件的初始化工做,如定义this.state的初始内容。
import React, { Component } from 'react'; class Test extends Component { constructor(props) { super(props); } }
第二个是组件的挂载(Mounting)阶段
此阶段分为componentWillMount,render,componentDidMount三个时期。
componentWillMount:
在组件挂载到DOM前调用,且只会被调用一次,在这边调用this.setState不会引发组件从新渲染,也能够把写在这边的内容提早到constructor()中,因此项目中不多用。
render:
根据组件的props和state(无二者的重传递和重赋值,论值是否有变化,均可以引发组件从新render) ,return一个React元素(描述组件,即UI),不负责组件实际渲染工做,以后由React自身根据此元素去渲染出页面DOM。render是纯函数(Pure function:函数的返回结果只依赖于它的参数;函数执行过程里面没有反作用),不能在里面执行this.setState,会有改变组件状态的反作用。
componentDidMount:
组件挂载到DOM后调用,且只会被调用一次
第三个是组件的更新(update)阶段
在讲述此阶段前须要先明确下react组件更新机制。setState引发的state更新或父组件从新render引发的props更新,更新后的state和props相对以前不管是否有变化,都将引发子组件的从新render。详细可看这篇文章
形成组件更新有两类(三种)状况:
1.父组件从新render
父组件从新render引发子组件从新render的状况有两种
a. 直接使用,每当父组件从新render致使的重传props,子组件将直接跟着从新渲染,不管props是否有变化。可经过shouldComponentUpdate方法优化。
class Child extends Component { shouldComponentUpdate(nextProps){ // 应该使用这个方法,不然不管props是否有变化都将会致使组件跟着从新渲染 if(nextProps.someThings === this.props.someThings){ return false } } render() { return <div>{this.props.someThings}</div> } }
b.在componentWillReceiveProps方法中,将props转换成本身的state
class Child extends Component { constructor(props) { super(props); this.state = { someThings: props.someThings }; } componentWillReceiveProps(nextProps) { // 父组件重传props时就会调用这个方法 this.setState({someThings: nextProps.someThings}); } render() { return <div>{this.state.someThings}</div> } }
根据官网的描述
在该函数(componentWillReceiveProps)中调用 this.setState() 将不会引发第二次渲染。
是由于componentWillReceiveProps中判断props是否变化了,若变化了,this.setState将引发state变化,从而引发render,此时就不必再作第二次因重传props引发的render了,否则重复作同样的渲染了。
class Child extends Component { constructor(props) { super(props); this.state = { someThings:1 } } shouldComponentUpdate(nextStates){ // 应该使用这个方法,不然不管state是否有变化都将会致使组件从新渲染 if(nextStates.someThings === this.state.someThings){ return false } } handleClick = () => { // 虽然调用了setState ,但state并没有变化 const preSomeThings = this.state.someThings this.setState({ someThings: preSomeThings }) } render() { return <div onClick = {this.handleClick}>{this.state.someThings}</div> } }
此阶段分为componentWillReceiveProps,shouldComponentUpdate,
componentWillUpdate,render,componentDidUpdate
卸载阶段
此阶段只有一个生命周期方法:componentWillUnmount
变动原因
原来(React v16.0前)的生命周期在React v16推出的Fiber以后就不合适了,由于若是要开启async rendering,在render函数以前的全部函数,都有可能被执行屡次。
原来(React v16.0前)的生命周期有哪些是在render前执行的呢?
getDerivedStateFromProps
static getDerivedStateFromProps(props, state) 在组件建立时和更新时的render方法以前调用,它应该返回一个对象来更新状态,或者返回null来不更新任何内容。
getDerivedStateFromProps 原本(React v16.3中)是只在建立和更新(由父组件引起部分),也就是否是由父组件引起,那么getDerivedStateFromProps也不会被调用,如自身setState引起或者forceUpdate引起。
这样的话理解起来有点乱,在React v16.4中改正了这一点,让getDerivedStateFromProps不管是Mounting仍是
Updating,也不管是由于什么引发的Updating,所有都会被调用,具体可看React v16.4 的生命周期图。
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate() 被调用于render以后,能够读取但没法使用DOM的时候。它使您的组件能够在可
能更改以前从DOM捕获一些信息(例如滚动位置)。今生命周期返回的任何值都将做为参数传递给
componentDidUpdate()。
官网给的例子:
class ScrollingList extends React.Component { constructor(props) { super(props); this.listRef = React.createRef(); } getSnapshotBeforeUpdate(prevProps, prevState) { //咱们是否要添加新的 items 到列表? // 捕捉滚动位置,以便咱们能够稍后调整滚动. if (prevProps.list.length < this.props.list.length) { const list = this.listRef.current; return list.scrollHeight - list.scrollTop; } return null; } componentDidUpdate(prevProps, prevState, snapshot) { //若是咱们有snapshot值, 咱们已经添加了 新的items. // 调整滚动以致于这些新的items 不会将旧items推出视图。 // (这边的snapshot是 getSnapshotBeforeUpdate方法的返回值) if (snapshot !== null) { const list = this.listRef.current; list.scrollTop = list.scrollHeight - snapshot; } } render() { return ( <div ref={this.listRef}>{/* ...contents... */}</div> ); }
范例:模拟redux存放全局状态,在组件间共享
import React from "react"; // 建立上下文 const Context = React.createContext(); // 获取Provider和Consumer const Provider = Context.Provider; const Consumer = Context.Consumer; // Child显示计数器,并能修改它,多个Child之间须要共享数据 function Child(props) { return <div onClick={() => props.add()}>{props.counter}</div>; } export default class ContextTest extends React.Component { // state是要传递的数据 state = { counter: 0 }; // add方法能够修改状态 add = () => { this.setState(nextState => ({ counter: nextState.counter + 1 })); }; // counter状态变动 render() { return ( <Provider value={{ counter: this.state.counter, add: this.add }}> {/* Consumer中内嵌函数,其参数是传递的数据,返回要渲染的组件 */} {/* 把value展开传递给Child */} <Consumer>{value => <Child {...value} />}</Consumer> <Consumer>{value => <Child {...value} />}</Consumer> </Provider> ); } }
// Hoc.js import React from "react"; // Lesson保证功能单一,它不关心数据来源,只负责显示 function Lesson(props) { return ( <div> {props.stage} - {props.title} </div> ); } // 模拟数据 const lessons = [ { stage: "React", title: "核心API" }, { stage: "React", title: "组件化1" }, { stage: "React", title: "组件化2" } ]; // 高阶组件withContent负责包装传入组件Comp // 包装后组件可以根据传入索引获取课程数据,真实案例中能够经过api查询获得 const withContent = Comp => props => { const content = lessons[props.idx]; // {...props}将属性展开传递下去 return <Comp {...content} />; }; // LessonWithContent是包装后的组件 const LessonWithContent = withContent(Lesson); export default function HocTest() { // HocTest渲染三个LessonWithContent组件 return ( <div> {[0,0,0].map((item, idx) => ( <LessonWithContent idx={idx} key={idx} /> ))} </div> ); }
范例:改造前面案例使上下文使用更优雅
// withConsumer是高阶组件工厂,它能根据配置返回一个高阶组件 function withConsumer(Consumer) { return Comp => props => { return <Consumer>{value => <Comp {...value} {...props} />}</Consumer>; }; } // Child显示计数器,并能修改它,多个Child之间须要共享数据 // 新的Child是经过withConsumer(Consumer)返回的高阶组件包装所得 const Child = withConsumer(Consumer)(function (props) { return <div onClick={() => props.add()} title={props.name}>{props.counter}</div>; }); export default class ContextTest extends React.Component { render() { return ( <Provider value={{ counter: this.state.counter, add: this.add }}> {/* 改造过的Child能够自动从Consumer获取值,直接用就行了 */} <Child name="foo"/> <Child name="bar"/> </Provider> ); } }
// 高阶组件withLog负责包装传入组件Comp // 包装后组件在挂载时能够输出日志记录 const withLog = Comp => { // 返回组件须要生命周期,所以声明为class组件 return class extends React.Component { render() { return <Comp {...this.props} />; } componentDidMount() { console.log("didMount", this.props); } }; }; // LessonWithContent是包装后的组件 const LessonWithContent = withLog(withContent(Lesson));
装饰器写法
// 装饰器只能用在class上 // 执行顺序从下往上 @withLog @withContent class Lesson2 extends React.Component { render() { return ( <div> {this.props.stage} - {this.props.title} </div> ); } } export default function HocTest() { // 这里使用Lesson2 return ( <div> {[0, 0, 0].map((item, idx) => ( <Lesson2 idx={idx} key={idx} /> ))} </div> ); } // 注意修改App.js中引入部分,添加一个后缀名 // 要求cra版本高于2.1.0
复合组件给与你足够的敏捷去定义自定义组件的外观和行为
组件复合
范例:Dialog组件负责展现,内容从外部传入便可,components/Composition.js
import React from "react"; // Dialog定义组件外观和行为 function Dialog(props) { return <div style={{ border: "1px solid blue" }}>{props.children}</div>; } export default function Composition() { return ( <div> {/* 传入显示内容 */} <Dialog> <h1>组件复合</h1> <p>复合组件给与你足够的敏捷去定义自定义组件的外观和行为</p> </Dialog> </div> ); }
范例:传个对象进去,key表示具名插槽
import React from "react"; // 获取相应部份内容展现在指定位置 function Dialog(props) { return ( <div style={{ border: "1px solid blue" }}> {props.children.default} <div>{props.children.footer}</div> </div> ); } export default function Composition() { return ( <div> {/* 传入显示内容 */} <Dialog> {{ default: ( <> <h1>组件复合</h1> <p>复合组件给与你足够的敏捷去定义自定义组件的外观和行为</p> </> ), footer: <button onClick={() => alert("react确实好")}>肯定</button> }} </Dialog> </div> ); }
若是传入的是函数,还能够实现做用域插槽的功能
function Dialog(props) { // 备选消息 const messages = { "foo": {title: 'foo', content: 'foo~'}, "bar": {title: 'bar', content: 'bar~'}, } // 执行函数得到要显示的内容 const {body, footer} = props.children(messages[props.msg]); return ( <div style={{ border: "1px solid blue" }}> {/* 此处显示的内容是动态生成的 */} {body} <div>{footer}</div> </div> ); } export default function Composition() { return ( <div> {/* 执行显示消息的key */} <Dialog msg="foo"> {/* 修改成函数形式,根据传入值生成最终内容 */} {({title, content}) => ({ body: ( <> <h1>{title}</h1> <p>{content}</p> </> ), footer: <button onClick={() => alert("react确实好")}>肯定</button> })} </Dialog> </div> ); }
若是props.children是jsx,此时它是不能修改的
范例:实现RadioGroup和Radio组件,可经过RadioGroup设置Radio的name
function RadioGroup(props) { // 不可行, // React.Children.forEach(props.children, child => { // child.props.name = props.name; // }); return ( <div> {React.Children.map(props.children, child => { // 要修改child属性必须先克隆它 return React.cloneElement(child, { name: props.name }); })} </div> ); } // Radio传入value,name和children,注意区分 function Radio({ children, ...rest }) { return ( <label> <input type="radio" {...rest} /> {children} </label> ); } export default function Composition() { return ( <div> {/* 执行显示消息的key */} <RadioGroup name="mvvm"> <Radio value="vue">vue</Radio> <Radio value="react">react</Radio> <Radio value="ng">angular</Radio> </RadioGroup> </div> ); }
状态钩子 State Hook
import React, { useState } from "react"; export default function HooksTest() { // useState(initialState),接收初始状态,返回一个由状态和其更新函数组成的数组 const [fruit, setFruit] = useState(""); return ( <div> <p>{fruit === "" ? "请选择喜好的水果:" : `您的选择是:${fruit}`}</p> </div> ); }
// 声明列表组件 function FruitList({fruits, onSetFruit}) { return ( <ul> {fruits.map(f => ( <li key={f} onClick={() => onSetFruit(f)}> {f} </li> ))} </ul> ); } export default function HooksTest() { // 声明数组状态 const [fruits, setFruits] = useState(["香蕉", "西瓜"]); return ( <div> {/*添加列表组件*/} <FruitList fruits={fruits} onSetFruit={setFruit}/> </div> ); }
// 声明输入组件 function FruitAdd(props) { // 输入内容状态及设置内容状态的方法 const [pname, setPname] = useState(""); // 键盘事件处理 const onAddFruit = e => { if (e.key === "Enter") { props.onAddFruit(pname); setPname(""); } }; return ( <div> <input type="text" value={pname} onChange={e => setPname(e.target.value)} onKeyDown={onAddFruit} /> </div> ); } export default function HooksTest() { // ... return ( <div> {/*添加水果组件*/} <FruitAdd onAddFruit={pname => setFruits([...fruits, pname])} /> </div> ); }
useEffect 给函数组件增长了执行反作用操做的能力。
反作用(Side Effect)是指一个 function 作了和自己运算返回值无关的事,好比:修改了全局变量、修改了传入的参数、甚至是 console.log(),因此 ajax 操做,修改 dom 都是算做反作用。
import { useEffect } from "react"; useEffect(()=>{ setTimeout(() => { setFruits(['香蕉','西瓜']) }, 1000); },[])// 设置空数组意为没有依赖,则反作用操做仅执行一次
若是反作用操做对某状态有依赖,务必添加依赖选项
useEffect(() => { document.title = fruit; }, [fruit]);
useEffect(() => { const timer = setInterval(() => { console.log('msg'); }, 1000); return function(){ clearInterval(timer); } }, []);
useReducer
useReducer是useState的可选项,经常使用于组件有复杂状态逻辑时,相似于redux中reducer概念。
import { useReducer } from "react"; // 添加fruit状态维护fruitReducer function fruitReducer(state, action) { switch (action.type) { case "init": return action.payload; case "add": return [...state, action.payload]; default: return state; } } export default function HooksTest() { // 组件内的状态不须要了 // const [fruits, setFruits] = useState([]); // useReducer(reducer,initState) const [fruits, dispatch] = useReducer(fruitReducer, []); useEffect(() => { setTimeout(() => { // setFruits(["香蕉", "西瓜"]); // 变动状态,派发动做便可 dispatch({ type: "init", payload: ["香蕉", "西瓜"] }); }, 1000); }, []); return ( <div> {/*此处修改成派发动做*/} <FruitAdd onAddFruit={pname => dispatch({type: 'add', payload: pname})} /> </div> ); }
useContext
useContext用于在快速在函数组件中导入上下文。
import React, { useContext } from "react"; // 建立上下文 const Context = React.createContext(); export default function HooksTest() { // ... return ( {/* 提供上下文的值 */} <Context.Provider value={{fruits,dispatch}}> <div> {/* 这里再也不须要给FruitAdd传递状态mutation函数,实现了解耦 */} <FruitAdd /> </div> </Context.Provider> ); } function FruitAdd(props) { // 使用useContext获取上下文 const {dispatch} = useContext(Context) const onAddFruit = e => { if (e.key === "Enter") { // 直接派发动做修改状态 dispatch({ type: "add", payload: pname }) setPname(""); } }; // ... }
Hooks相关拓展