CDN链接
React和ReactDOM都可通过CDN获得。
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
上述版本仅用于开发,不适合生产。React的缩小和优化生产版本可在以下位置获得:
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
React API 与 ReactDOM API
ReactDOM.render:function;
React.createElement:function; // 创建ReactElement元素
React.cloneElement:function; // 克隆ReactElement元素
// 创建一个context对象,里面包含Provider,Consumer对象
React.createContext(defaultValue: any):function;
React.Component:function; // React对象原型
React.PureComponent:function // React对象原型
React.createRef:function; // 创建ref
React.forwardRef:function; // 转发ref
// 配合React.laze使用
React.Suspense: string; [səˈspens]
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
/* 配合import()使用,import()方法是用来代替require,实现动态加载;
动态 import() 语法目前只是一个 ECMAScript (JavaScript) 提案,
而不是正式的语法标准。预计在不远的将来就会被正式接受。
当 Webpack 解析到该语法时,它会自动地开始进行代码分割。
当使用 Babel 时,你要确保 Babel 能够解析动态 import 语法而不是将其进行转换,对于这一要求你需要 babel-plugin-syntax-dynamic-import 插件。
注意:babel-plugin-syntax-dynamic-import 插件的作用是解析识别import()动态导入语法---并非转换,而是解析识别
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
*/
React.lazy:function;
React的Context的使用
- Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据。
- 一种无需context的解决方案是将组件自身传递下去,组合组件以及高阶组件的使用,已解决状态传递的问题 ```javascript const ThemeContext = React.createContext(‘light’);
class App extends React.Component {
state = {
name: ‘dark’
}
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
// 中间的组件再也不必指明往下传递 theme 了。 function Toolbar() { return (
class ThemedButton extends React.Component { // 指定 contextType 读取当前的 theme context。 // React 会往上找到最近的 theme Provider,然后使用它的值。 // 在这个例子中,当前的 theme 值为 “dark”。 static contextType = ThemeContext; render() { console.log(this.context); return
ReactDOM.render(
<a name="jfq0e"></a>
### `Class.contextType`
挂载在 class 上的 `contextType` 属性会被重赋值为一个由 [`React.createContext()`](https://zh-hans.reactjs.org/docs/context.html#reactcreatecontext) 创建的 Context 对象。这能让你使用 `this.context` 来消费**最近 **Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。
你只通过该 API 订阅单一 context。如果你想订阅多个,阅读[使用多个 Context](https://zh-hans.reactjs.org/docs/context.html#consuming-multiple-contexts)
```javascript
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* 基于这个值进行渲染工作 */
}
}
Context.Consumer
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
这里,React 组件也可以订阅到 context 变更。这能让你在函数式组件中完成订阅 context。
这需要函数作为子元素(function as a child)这种做法。这个函数接收当前的 context 值,返回一个 React 节点。传递给函数的 value
值等同于往上组件树离这个 context 最近的 Provider 提供的 value
值。如果没有对应的 Provider,value
参数等同于传递给 createContext()
的 defaultValue
。
Context.displayName
context 对象接受一个名为 displayName
的 property,类型为字符串。React DevTools 使用该字符串来确定 context 要显示的内容。
示例,下述组件在 DevTools 中将显示为 MyDisplayName:
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
Context.Provider
<MyContext.Provider value={/* 某个值 */}>
每个Context 对象都会返回一个Provider React 组件,它允许消费组件订阅context 的变化。
Provider接收一个value
属性,传递给消费组件。一个Provider可以和多个消费组件有对应关系。多个Provider也可以嵌套使用,里层的会覆盖外层的数据。
当Provider的value
值发生变化时,它内部的所有消费组件都会发送变化。Provider及其内部Consumer组件都不受制于shouldComponentUpdate
函数,因此当Consumer组件在其祖先组件退出更新的情况下也能更新。
通过新旧值检测来确定变化,使用了与Object.is
相同的算法。
因为 context 会使用参考标识(reference identity)来决定何时进行渲染,这里可能会有一些陷阱,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。举个例子,当每一次 Provider 重渲染时,以下的代码会重渲染所有下面的 consumers 组件,因为 value
属性总是被赋值为新的对象:
class App extends React.Component {
render() {
return (
<Provider value={{something: 'something'}}>
<Toolbar />
</Provider>
);
}
}
为了防止这种情况,将 value 状态提升到父节点的 state 里:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
React 源码分析项目
https://github.com/a8397550/react-source-share.git
JSX解析
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
// React 元素
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!' // children: [ReactElement, ...] children: ReactElement
}
};
function elementFn (props) {
return React.createElement(
'h1',
{className: 'greeting'},
React.createElement('div', {},
React.createElement('div', {}, props.titleA),
React.createElement('div', {}, props.titleB)
),
'hello world'
)
}
var Son = React.createElement('div', {
children: '888',
componentDidMount: function() {
// 这玩意不会被执行,不是生命周期,会提示Warning,
// 只有React.createElement(fn:function, {});的React元素才有生命周期
}
})
var Parent = (function (Component) {
Mod.prototype = Object.create(Component.prototype);
Mod.prototype.constructor = Mod;
Mod.__proto__ = Component;
function Mod(props) {
var _this;
_this = Component.call(this, props) || this;
_this.state = {
name: 'TestMod'
};
this.componentDidMount = function () {
console.log('componentDidMount')
}
return _this;
}
Mod.getDerivedStateFromProps = function () {
console.log('888')
return null;
}
Mod.prototype.render = function () {
const self = this;
console.log(self.state.name);
return React.createElement(
'div',
{
children: self.state.name,
key: 'Parent',
id: 'Parent',
onClick: () => {
self.setState({
name: 'Parent',
})
}
}
);
}
return Mod;
}(React.Component))
ReactDOM.render(React.createElement(Parent, null), document.getElementById('root'))
// ReactDOM.render(Son, document.getElementById('root'))
Diff变化
React.setState造成的组件变化,考虑React.Context变化情况,因为React.Context不受shouldComponentUpdate的影响
1. 根元素
假设你的 HTML 文件某处有一个 <div>
:<div id="root"></div>
我们将其称为“根” DOM 节点,因为该节点内的所有内容都将由 React DOM 管理。
想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入 ReactDOM.render()
:
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
2. React 只更新它需要更新的部分
React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。
考虑一个计时器的例子:
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);
尽管每一秒我们都会新建一个描述整个 UI 树的元素,React DOM 只会更新实际改变了的内容.
3. 组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
/*
让我们来回顾一下这个例子中发生了什么:
我们调用 ReactDOM.render() 函数,并传入 <Welcome name="Sara" /> 作为参数。
React 调用 Welcome 组件,并将 {name: 'Sara'} 作为 props 传入。
Welcome 组件将 <h1>Hello, Sara</h1> 元素作为返回值。
React DOM 将 DOM 高效地更新为 <h1>Hello, Sara</h1>。
*/
4. 组件的自我更新
函数组件与class组件(还可以通过React.createElement()创建组件)
我们希望组件只被构建一次,它可以自我更新,使用state属性与setState({…})方法
出于性能考虑,React 可能会把多个 setState()
调用合并成一个调用。
因为 this.props
和 this.state
可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
这里的合并是浅合并,所以 this.setState({comments})
完整保留了 this.state.posts
, 但是完全替换了 this.state.comments
。
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
// 会与下面的执行合并
this.setState({
posts: response.posts
});
// 会与下面的执行合并
this.setState({
comments: response.comments
});
}
5. 数据是向下流动的
不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。
这通常会被叫做“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。
注意 Props 的只读性
组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props。来看下这个 sum
函数:
function sum(a, b) {
return a + b;
}
这样的函数被称为“纯函数”,因为该函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。
相反,下面这个函数则不是纯函数,因为它更改了自己的入参:
function withdraw(account, amount) {
account.total -= amount;
}
React 非常灵活,但它也有一个严格的规则:
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
注意 在 React 中另一个不同点是你不能通过返回 false
的方式阻止默认行为。
注意 确定 UI state 的最小(且完整)理解你的应用变化是很困难的
通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state:
- 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
- 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
- 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。
我们已经确定了应用所需的 state 的最小集合。接下来,我们需要确定哪个组件能够改变这些 state,或者说拥有这些 state。
注意:React 中的数据流是单向的,并顺着组件层级从上往下传递。哪个组件应该拥有某个 state 这件事,对初学者来说往往是最难理解的部分。尽管这可能在一开始不是那么清晰,但你可以尝试通过以下步骤来判断:
对于应用中的每一个 state:
- 找到根据这个 state 进行渲染的所有组件。
- 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
- 该共同所有者组件或者比它层级更高的组件应该拥有该 state。
- 如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置于高于共同所有者组件层级的位置。