til
  • README
  • Software Development Roles
  • solid
  • README
    • service-worker
  • docker
    • arg
    • 更新docker版本
  • editor
    • vscode
    • Creating a VS Code Theme
  • english
    • words
  • front-end
    • ==
    • ECMAScript
    • IIFE
    • Label
    • basic
    • html.js.css渲染顺序
    • npm-vs-yarn
    • obj-delete-key-value
    • react
    • split-join-and-replace
    • video
    • 前端自检清单
    • 递归及去重
    • css
      • css换肤
      • flex
      • list
      • nth-child和nth-of-type区别
      • padding
      • position
      • 层叠上下文
      • 层叠样式(+)
      • 正方形
      • 语义化标签
    • dom
      • DOCTYPE
      • HEAD
      • 修改document
      • 自定义表单验证
    • electron
      • basic
    • es6
      • basic-type
      • basic
      • prototype-example
      • defineProperty
      • understanding-es6
        • 0.introduction
        • Appendix A: Smaller Changes
        • Appendix B: Understanding ES7
        • Block-Binding
        • Proxies&Reflection
        • class
        • 解构赋值
        • function
        • improved-array
        • iterators&generators
        • modules
        • object
        • promise
        • Map&Set
        • symbol
    • images
      • 前端角度看图片
    • interview_case
      • lexical_scope
      • redux和localstroage存储位置
    • javascript
      • fuck-the-js
      • js-engine-work
      • js原生操作dom
      • what-is-function-program
      • 执行上下文
      • articles
        • JavaScript中使用函数组合
        • JavaScript中的依赖注入
        • JavaScript作用域链中的标识符解析和闭包
        • JavaScript是何如工作的--概述
        • JavaScript深拷贝
        • JavaScript的全局变量是如何工作的
        • js继承常见的误解
        • node12&chrome中7个新的提案功能
        • 你真的懂JavaScript吗
      • date
        • index
      • engines
        • basic
        • JavaScript引擎基础:外形和内联缓存
        • v8中推测性优化的介绍
        • 优化原型
        • 更快的异步功能和promise
      • events
        • baisc
        • 事件冒泡和捕获
        • 定义事件
        • 页面生命周期
      • higher-order-function
        • curry
        • monad
      • module
        • basic
        • main&module
      • objects
        • iterator
        • spread
        • examples
          • iterator
      • performance
        • blocking-css
        • cache
      • prototype
        • Property-Descriptors
        • basic
        • prototype-shadow
      • you-dont-known-js
        • async&performance
          • Chapter 1: Asynchrony: Now & Later
          • Chapter 2: Callbacks
          • Chapter 3: Promises
          • Chapter 4: Generators
        • scope & closures
          • apA
          • apB
          • apC
          • apD
          • chapter1-what-is-scope
          • chapter2-lexical-scope
          • chapter3-function-vs-block-scope
          • chapter4-hoisting
          • chapter5-scope-closure
        • this & object prototypes
          • chapter1-this-or-that
          • chapter2-this-make-sense
          • chapter3-objects
          • chapter4-mixing(up)-class-object
          • chapter5-prototype
          • chapter6-behavior-delegation
        • types&grammer
          • Chapter1-Types
          • Chapter2-Values
          • Chapter3-Natives
          • Chapter4-coercion
          • Chapter5-grammer
        • up & going
          • chapter1-into-programming
          • chapter2-into-javascript
          • chapter3-into-YDKJS
    • mobile
      • iPhone分辨率终极指南
    • npm
      • arguments
      • build
    • react-native
      • prop-methods
    • react
      • PropTypes
      • basic
      • codebase-overview
      • component-element-instance
      • context
      • how-to-known-component-is-func-or-class
      • overview
      • react16.9
      • react18计划
      • react的设计原则
      • reconciliation
      • setState
      • useMemo
      • why-do-we-write-super-props
      • 从头实现一个react
      • concurrent
        • 引入并发模式(仅试验)
      • conf
        • conf-2019
      • events
        • 合成事件概述
      • hooks
        • custom-hook
        • effect-hook
        • hooks-api
        • intro
        • overview
        • rules
        • state-hook
        • hooks-vs-class
          • thinking-in-react-hooks
      • overreact
        • Development模式是如何工作的
        • How-Does-setState-Know-What-to-Do
        • Why-Do-React-Elements-Have-a-$$typeof-Property
        • Why-Do-React-Hooks-Rely-on-Call-Order
        • how-to-known-component-is-func-or-class
        • preparing-tach-talk-motivation
        • react作为ui运行
        • things-i-dont-known-as-2018
        • ui-element-problem-and-build-yourself
        • why-do-we-write-super-props
        • 一份完整的useEffect指南
        • 为什么X不是Hook
        • 函数组件与类有什么不同?
        • 演讲准备2-what-why-how
        • 编写弹性组件
        • 让setInterval在React-Hooks中为声明式
      • practice
        • render
      • react-dom
        • basic
      • react-redux
        • apiv7.1-hooks
        • connect
        • shallow-equal
      • redux
        • applyMiddleware
        • applyMiddleware2-细节
        • example
    • regex
      • index
    • stories
      • 数组下标
      • 阻止事件冒泡
    • svelte
      • compile-svelte-in-your-head-1
      • compiler-overview
      • parser
        • 写一个解析器-JavaScript的JSON解析器
    • turbopack
      • basic
    • typescript
      • interface和type的区别
    • webpack
      • hash
      • webpack4-for-react
      • webpack4
      • webpack4to5
      • babel
        • babel-parser和acorn的区别
        • babel.7.11
        • family
        • react16.14使用new-transform
        • update-to-7
    • pdf
      • deep-js
        • basic
      • react
        • reintroducing
  • git
    • capital
    • emoji
  • http
    • http2.0
    • response
  • rails
    • api
    • flash
    • middleware-vs-metal
    • model
    • performance
    • routes
    • environment
      • error
    • patterns
      • service
    • sidekiq
      • params
    • deploy
      • capistrano
        • ssh
  • ruby
    • self
    • net
      • http请求携带cookie
  • server
    • ss
    • ssh
    • user
    • crawler
      • puppeteer
    • nginx
      • domain-without-80
      • nginx节省带宽
  • sql
    • rails
    • search
Powered by GitBook
On this page
  1. front-end
  2. react

how-to-known-component-is-func-or-class

PreviouscontextNextoverview

Last updated 6 years ago

原文:

考虑这个定义为函数的Greeting组件:

function Greeting() {
  return <p>Hello</p>;
}

react同样支持作为一个类去定义它:

class Greeting extends React.Component {
  render() {
    return <p>Hello</p>;
  }
}

(直到,这是使用状态等功能的唯一方法。)

当你想渲染<Greeting />组件时,你不必关心它是如何定义的:

//类或者函数,都可以
<Greeting />

但是,作为react本身,他是关心这些差异的!

如果Greeting是一个函数,react需要调用他:

// 你的代码
function Greeting() {
  return <p>Hello</p>;
}

// React内
const result = Greeting(props); // <p>Hello</p>

但是如果Greeting是一个类,那么React就需要使用new来实例化它,然后在实例上调用render方法:

// 你的代码
class Greeting extends React.Component {
  render() {
    return <p>Hello</p>;
  }
}

// React内
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>

在这两种情况下,React的目标是获取渲染节点(本例中,是<p> Hello </ p>)。

那么React如何知道某个东西是类还是函数呢?

这个博客是为了好奇于想知道React为何以某种方式运作的读者。你是那个人吗?然后让我们一起挖掘。

这是一段漫长的旅程。系好安全带。这篇文章没有太多关于React本身的信息,但我们将讨论new,this,class,arrow function,prototype,__ proto__,instanceof以及这些东西如何在JavaScript中协同工作的一些方面。幸运的是,当你使用React时,你不需要考虑那些。如果你正在实现React ......

(如果你真的只想知道答案,请拉动到最后。)

首先,我们需要理解为什么以不同方式处理函数和类很重要。注意我们在调用类时如何使用new运算符:

// If Greeting is a function
const result = Greeting(props); // <p>Hello</p>

// If Greeting is a class
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>

让我们大致的了解下new在Javascript中做了什么事。

在过去(ES6之前),Javascript没有类。但是,可以使用普通函数表现出于类相似的模式。 具体来说,您可以在类似于类构造函数的角色中使用任何函数,方法是在调用之前添加new:

// 只是一个function
function Person(name) {
  this.name = name;
}

var fred = new Person('Fred'); // ✅ Person {name: 'Fred'}
var george = Person('George'); // 🔴 不会如期工作

你今天仍然可以写这样的代码!在DevTools中尝试一下。

如果不携带new调用Person('Fred'),this在里面会指向全局和无用的东西(例如,窗口或未定义)。所以我们的代码会崩溃或者像设置window.name一样愚蠢。

通过在调用之前添加new,等于说:“嘿JavaScript,我知道Person只是一个函数,但让我们假设它类似于类构造函数。 创建一个{}对象并在Person函数内将this指向该对象,这样我就可以分配像this.name这样的东西。然后把那个对象返回给我。”

上面这些就是new操作符做的事情。

var fred = new Person('Fred'); // `Person`内,相同的对象作为`this`

new操作符使得返回的fred对象可以使用Person.prototype上的任何内容。

function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  alert('Hi, I am ' + this.name);
}

var fred = new Person('Fred');
fred.sayHi();

这是人们在JavaScript直接添加类之前模拟类的方式。

所以new在JavaScript已经存在了一段时间。但是,class是新加的的。现在让我们使用class重写上面的代码以更紧密地匹配我们的意图:

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    alert('Hi, I am ' + this.name);
  }
}

let fred = new Person('Fred');
fred.sayHi();

在语言和API设计中捕获开发人员的意图非常重要。

如果你编写一个函数,JavaScript就无法猜测它是否像alert()一样被调用,或者它是否像new Person()那样充当构造函数。忘记为像Person这样的函数指定new会导致混乱的行为。

类语法让相当于说:“这不仅仅是一个函数 - 它是一个类,它有一个构造函数”。 如果在调用它时忘记使用new,JavaScript将引发错误:

let fred = new Person('Fred');
// ✅  If Person is a function: works fine
// ✅  If Person is a class: works fine too

let george = Person('George'); // We forgot `new`
// 😳 If Person is a constructor-like function: confusing behavior
// 🔴 If Person is a class: fails immediately

这有助于我们尽早发现错误,而不是等待像this.name这样的一些模糊的bug被视为window.name而不是george.name。

但是,这意味着React需要在调用任何类之前使用new。它不能只是将其作为常规函数调用,因为JavaScript会将其视为错误!

class Counter extends React.Component {
  render() {
    return <p>Hello</p>;
  }
}

// 🔴 React can't just do this:
const instance = Counter(props);

这意味着麻烦。

在我们看到React如何解决这个问题之前,重要的是要记住大多数人在React中使用像Babel这样的编译器来编译现代功能,比如旧浏览器的类。所以我们需要在设计中考虑编译器。

在Babel的早期版本中,可以在没有new的情况下调用类。但是,这被修复了 - 通过生成一些额外的代码:

function Person(name) {
  // A bit simplified from Babel output:
  if (!(this instanceof Person)) {
    throw new TypeError("Cannot call a class as a function");
  }
  // Our code:
  this.name = name;
}

new Person('Fred'); // ✅ Okay
Person('George');   // 🔴 Can’t call class as a function

你或许在你打包的代码中看到类似上面这的,这就是所有_classCallCheck函数的功能。 (您可以通过选择进入“松散模式”而不进行检查来减小捆绑包大小,但这可能会使您最终转换为真正的原生类变得复杂。)

到现在为止,你应该大致了解使用new或不使用new来调用某些内容之间的区别:

new Person()

Person()

class

✅ this is a Person instance

🔴 TypeError

function

✅ this is a Person instance

😳 this is window or undefined

这就是React正确调用组件的重要原因。 如果您的组件被定义为类,React在调用它时需要使用new。

所以React可以检查某个东西是不是一个类?

好吧,也许React可以在每次调用时使用new?不幸的是,这并不总是奏效。

作为常规函数,使用new调用它们会为它们提供一个对象实例作为this。对于作为构造函数编写的函数(如上面的Person),它是理想的,但它会使函数组件混淆:

function Greeting() {
  // 我们不希望“this”在这里成为任何一种情况下的实例
  return <p>Hello</p>;
}

但这可能是可以容忍的。还有另外两个原因可以扼杀一直使用new的想法。

第一个可以扼杀的原因是因为箭头函数,来试试:

const Greeting = () => <p>Hello</p>;
new Greeting(); // 🔴 Greeting is not a constructor

这种行为是有意的,并且遵循箭头函数的设计。箭头函数的主要优点之一是它们没有自己的this绑定 - 相反,这是从最接近的常规函数​​解决的:

class Friends extends React.Component {
  render() {
    const friends = this.props.friends;
    return friends.map(friend =>
      <Friend
        // `this` is resolved from the `render` method
        size={this.props.size}
        name={friend.name}
        key={friend.id}
      />
    );
  }
}

好的,所以箭头功能没有自己的this。 但这意味着他们作为构造者将完全无用!

const Person = (name) => {
  // 🔴 This wouldn’t make sense!
  this.name = name;
}

因此,JavaScript不允许使用new调用箭头函数。如果你这样做,无论如何你可能犯了一个错误,最好早点告诉你。这类似于JavaScript不允许在没有new的情况下调用类的方式。

这很不错,但它也影响了我们的计划。 React不可以在所有内容上调用new,因为它会破坏箭头函数!我们可以尝试通过缺少prototype来检测箭头功能,而不仅仅是new:

(() => {}).prototype // undefined
(function() {}).prototype // {constructor: f}

我们不能总是使用new的另一个原因是它会阻止React支持返回字符串或其他原始类型的组件。

function Greeting() {
  return 'Hello';
}

Greeting(); // ✅ 'Hello'
new Greeting(); // 😳 Greeting {}

但是,JavaScript还允许使用new调用的函数通过返回一些其他对象来覆盖new的返回值。据推测,这被认为对于我们想要重用实例的池这样的模式很有用:

// Created lazily
var zeroVector = null;

function Vector(x, y) {
  if (x === 0 && y === 0) {
    if (zeroVector !== null) {
      // Reuse the same instance
      return zeroVector;
    }
    zeroVector = this;
  }
  this.x = x;
  this.y = y;
}

var a = new Vector(1, 1);
var b = new Vector(0, 0);
var c = new Vector(0, 0); // 😲 b === c

但是,如果函数不是对象,new也会完全忽略函数的返回值。如果你返回一个字符串或一个数字,就好像没有显示返回。

function Answer() {
  return 42;
}

Answer(); // ✅ 42
new Answer(); // 😳 Answer {}

使用new调用函数时,无法从函数中读取原始返回值(如数字或字符串)。因此,如果React总是使用new,它将无法添加返回字符串的支持组件!

这是不可接受的,所以我们需要妥协。

到目前为止我们学到了什么? React需要用new调用类(包括Babel输出),但它需要调用常规函数或箭头函数(包括Babel输出)而不需要new。并没有可靠的方法来区分它们(类和函数)。

如果我们无法解决一般问题,我们能解决一个更具体的问题吗?

将组件定义为类时,您可能希望为内置方法(如this.setState())扩展React.Component。我们可以只检测React.Component后代,而不是尝试检测所有类吗?

剧透:这正是React所做的。

也许,检查Greeting是否是React组件类的惯用方法是测试Greeting.prototype instanceof React.Component:

class A {}
class B extends A {}

console.log(B.prototype instanceof A); // true

我知道你在想什么。刚刚发生了什么?!要回答这个问题,我们需要了解JavaScript原型。

你可能熟悉原型链。JavaScript中的每个对象都可能有一个“原型”。当我们编写fred.sayHi()但fred对象没有sayHi属性时,我们在fred的原型上查找sayHi属性。如果我们在那里找不到它,我们会看看链中的下一个原型--fred的原型的原型。等等。

令人困惑的是,类或函数的prototype属性并不指向该值的原型。 我不是在开玩笑。

function Person() {}

console.log(Person.prototype); // 🤪 Not Person's prototype
console.log(Person.__proto__); // 😳 Person's prototype

所以“原型链”更像是__proto __.__ proto __.__ proto__而不是prototype.prototype.prototype。这花了我多年才得到。

那么函数或类的原型属性是什么呢? 它是proto赋予所有使用该类或函数创建的对象!

function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  alert('Hi, I am ' + this.name);
}

var fred = new Person('Fred'); // Sets `fred.__proto__` to `Person.prototype`

而proto链是JavaScript查找属性的方式:

fred.sayHi();
// 1. Does fred have a sayHi property? No.
// 2. Does fred.__proto__ have a sayHi property? Yes. Call it!

fred.toString();
// 1. Does fred have a toString property? No.
// 2. Does fred.__proto__ have a toString property? No.
// 3. Does fred.__proto__.__proto__ have a toString property? Yes. Call it!

在实践中,除非您正在调试与原型链相关的内容,否则您几乎不需要直接从代码中触及__proto__。如果你想在fred .__ proto__上提供东西,你应该把它放在Person.prototype上。至少这是它最初设计的方式。

__proto__属性甚至不应该被浏览器暴露,因为原型链被认为是一个内部概念。但是有些浏览器添加了__proto__,最终它被勉强标准化(但赞成使用Object.getPrototypeOf())。

然而,我仍然发现一个名为prototype的属性没有给你一个值的原型(例如,fred.prototype未定义,因为fred不是一个函数),这让我感到非常困惑。就个人而言,我认为这是即使是经验丰富的开发人员也会误解JavaScript原型的最大原因。

这是一个很长的帖子,嗯?我说我们80%在那里。保持着。

我们知道,当说obj.foo时,JavaScript实际上在obj,obj .__ proto__,obj .__ proto __.__ proto__中寻找foo,依此类推。

对于类,您不会直接暴露于此机制,但扩展也适用于良好的旧原型链。这就是我们的React类的实例如何访问setState之类的方法:

class Greeting extends React.Component {
  render() {
    return <p>Hello</p>;
  }
}

let c = new Greeting();
console.log(c.__proto__); // Greeting.prototype
console.log(c.__proto__.__proto__); // React.Component.prototype
console.log(c.__proto__.__proto__.__proto__); // Object.prototype

c.render();      // Found on c.__proto__ (Greeting.prototype)
c.setState();    // Found on c.__proto__.__proto__ (React.Component.prototype)
c.toString();    // Found on c.__proto__.__proto__.__proto__ (Object.prototype)

换句话说,当使用类时,实例的__proto__链“镜像”到类层次结构:

// `extends` chain
Greeting
  → React.Component
    → Object (implicitly)

// `__proto__` chain
new Greeting()
  → Greeting.prototype
    → React.Component.prototype
      → Object.prototype

由于__proto__链反映了类层次结构,因此我们可以通过从Greeting.prototype开始检查Greeting是否扩展了React.Component,然后跟随其__proto__链:

// `__proto__` chain
new Greeting()
  → Greeting.prototype // 🕵️ We start here
    → React.Component.prototype // ✅ Found it!
      → Object.prototype

方便的是,x instanceof Y确实完成了这种搜索。它遵循x .__ proto__链在那里寻找Y.prototype。

通常,它用于确定某些东西是否是类的实例:

let greeting = new Greeting();

console.log(greeting instanceof Greeting); // true
// greeting (🕵️‍ We start here)
//   .__proto__ → Greeting.prototype (✅ Found it!)
//     .__proto__ → React.Component.prototype 
//       .__proto__ → Object.prototype

console.log(greeting instanceof React.Component); // true
// greeting (🕵️‍ We start here)
//   .__proto__ → Greeting.prototype
//     .__proto__ → React.Component.prototype (✅ Found it!)
//       .__proto__ → Object.prototype

console.log(greeting instanceof Object); // true
// greeting (🕵️‍ We start here)
//   .__proto__ → Greeting.prototype
//     .__proto__ → React.Component.prototype
//       .__proto__ → Object.prototype (✅ Found it!)

console.log(greeting instanceof Banana); // false
// greeting (🕵️‍ We start here)
//   .__proto__ → Greeting.prototype
//     .__proto__ → React.Component.prototype 
//       .__proto__ → Object.prototype (🙅‍ Did not find it!)

但它确定一个类是否扩展另一个类也可以正常工作:

console.log(Greeting.prototype instanceof React.Component);
// greeting
//   .__proto__ → Greeting.prototype (🕵️‍ We start here)
//     .__proto__ → React.Component.prototype (✅ Found it!)
//       .__proto__ → Object.prototype

那个检查是我们如何确定某些东西是React组件类还是常规函数。

但这并不是React所做的。 😳

最初的标志位于React.Component类的基础上:

// Inside React
class Component {}
Component.isReactClass = {};

// We can check it like this
class Greeting extends Component {}
console.log(Greeting.isReactClass); // ✅ Yes

这就是React将此标志移动到React.Component.prototype的原因:

// Inside React
class Component {}
Component.prototype.isReactComponent = {};

// We can check it like this
class Greeting extends Component {}
console.log(Greeting.prototype.isReactComponent); // ✅ Yes

这实际上就是它的全部内容。

isReactComponent检查在今天的React中使用。

实际的解决方案非常简单,但我接着解释了为什么React最终得到了这个解决方案,以及替代方案是什么。

根据我的经验,库API通常就是这种情况。 为了使API易于使用,经常需要考虑语言语义(可能,对于多种语言,包括未来的方向),运行时性能,有和没有编译时步骤的人体工程学,生态系统和包装解决方案的状态, 早期预警和许多其他事情。 最终结果可能并不总是最优雅,但它必须是实用的。

如果最终API成功,则其用户永远不必考虑此过程。 相反,他们可以专注于创建应用程序。

但如果你也好奇......很高兴知道它是如何运作的。

就像在我一样,你不需要知道this在React中的所作所为。多年来我一直都不知道。请不要把它变成面试问题。事实上,这篇文章更多的是关于JavaScript而不是关于React。

没有那么容易!即使我们可以,这仍然不适用于像Babel这样的工具处理的类。对于浏览器来说,它们只是简单的功能。

但是这个于使用babel编译的函数。这可能不是什么大问题,但还有另一个原因让这种方法成为死胡同。

这再次与怪癖有关。正如我们之前看到的那样,new告诉JavaScript引擎创建一个对象,在函数内部创建该对象,然后将该对象作为new的结果。

对于instanceof解决方案的一个警告是,当页面上有多个React副本时它不起作用,而我们正在检查的组件继承自另一个React副本的React.Component。在一个项目中混合使用React的多个副本是不好的,原因有几个,但从历史上看,我们尽可能避免出现问题。 (使用Hooks,我们强制重复数据删除。)

另一种可能的启发式方法可能是检查原型上是否存在渲染方法。但是,当时还组件API将如何发展。每张支票都有成本,所以我们不想添加多张。如果将render定义为实例方法(例如使用类属性语法),这也不起作用。

因此,React为基本组件了一个特殊标志。React检查是否存在该标志,这就是它如何知道某些东西是否是React组件类还是函数。

但是,我们想要定位的一些类实现没有静态属性(或设置非标准__proto__),因此标志丢失了。

您可能想知道为什么它是一个对象而不仅仅是一个布尔值。它在实践中并不重要,但早期版本的Jest(在Jest为Good™️之前)默认启用了自动锁定功能。生成的mocks省略了原始属性,。感谢Jest。

如果不扩展React.Component,React将不会在原型上找到isReactComponent,也不会将组件视为类。现在你知道为什么是: Cannot call a class as a function错误的答案是添加extends React.Component。最后,添加了一个,当prototype.render存在时会发出警告,但prototype.isReactComponent不存在。

how-does-react-tell-a-class-from-a-function
最近
之前的帖子中
用JavaScript中的函数告诉一个类
不适用
new的设计
可能需要
不清楚
添加
复制
打破了检查
最受欢迎的回答
警告