From 66a0ada30bed8f9a6a578612fe90416d1583aaa5 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 4 Dec 2019 10:43:59 -0500 Subject: [PATCH 1/5] Draft 1 First proposal for Signals and Slots. --- text/0000-signals-and-slots.md | 181 +++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 text/0000-signals-and-slots.md diff --git a/text/0000-signals-and-slots.md b/text/0000-signals-and-slots.md new file mode 100644 index 00000000..bdd126ac --- /dev/null +++ b/text/0000-signals-and-slots.md @@ -0,0 +1,181 @@ +- Start Date: (2019-12-04) +- RFC PR: (leave this empty) +- React Issue: (leave this empty) + +# Summary + +This is intended to introduce the concept of signals and slots used in Qt to +React. + +Signals and slots would be extremely useful for React. +It would solve the ongoing problem of child-to-child communication and will +lead to less tightly-coupled components. + +# Basic example + +```js +import { useSignal, connect } from 'react'; +import PropTypes from 'prop-types'; + +function Button ({ name }) { + const emit = useSignal('clicked'); + + const onClick = () => emit(`Hello, ${name}!`); + + return ( + + ); +} + +Button.signals = { + clicked: PropTypes.string +}; + +function Banner ({ message }) { + return ( +

{message}

+ ); +} + +Banner.propTypes = { + message: PropTypes.string +}; + +function App () { + const MyButton = Button; + const MyBanner = Banner; + connect(MyButton, 'clicked', MyBanner, 'message'); + + return ( + <> + + + + ); +} +``` + +# Motivation + +Child-to-child communication is notoriously difficult in React, but it's also +inherent to React. It should be solvable using React, not something that forces +developers to rely on external libraries. Signals and slots could solve the +problem of child-to-child communication in a natural, React-like way. + +Because the signals and slots API is implemented using React Hooks, it shares +the same benefits of being **completely optional** and +**100% backwards-compatible**. + +# Detailed design + +The signals and slots concept in Qt has been one of the most successful in the +history of software engineering. It's typesafe, decoupled, extensible, and +*fun*! It enables components to communicate with their parents or siblings, +without being tightly coupled to either. + +In Qt, slots are methods defined in a C++ class, and signals are functions +defined in a class but are implemented by a preproccessor, not the developer. +At first glance, it might seem like adapting that to React would require a lot +of extra code since React components are just functions. +But React components already have a mechanism for slots: JSX props. +Instead of slots being functions as they are in Qt, slots are parameters. + +The `connect` syntax is really where things get difficult. +The syntax Qt uses is something like +`connect(MyTimer, QTimer::fired, MyWindow, WindowClass::update)`. Obviously +that can't be directly ported to React. + +The primary reason for requiring any signals or slots to be defined at +compile-time is because they have to be implemented using Qt's +meta-object preprocessor. +In React, specifying signals and slots as string names means that all +components continue to work as-is without breaking anything. +It also has the benefit of backwards compatibility: New components that emit +signals can be connected to components that haven't been updated in years. + +In this proposal: +*Signals* are events that are defined by React components using the `useSignal` +hook. +*Slots* are React props that have been *connected* to a signal using the +`connect` hook. + +# Drawbacks + +Most of the drawbacks of React Hooks also apply to here. A non-exhaustive +list of drawbacks specific to this proposal include: + +- Will probably require considerable changes to the internals of React. +- Might be difficult to implement a proof-of-concept outside of React. +- `connect()` might need to be implemented by the renderer (ReactDOM) rather + than React itself. +- If documentation is unclear it could easily lead to confusion about how this + is used and what to use it for. +- Additional prop-types are needed for signals in order to guarentee type safety. +- The current design uses strings as params for `connect()`, which makes + type-checking with TypeScript/Flow much more difficult. +- Components have to be assigned variable names (MyButton) to avoid unwanted + connections (e.g. multiple Banners but only one needs to be connected). + +# Alternatives + +I considered something like +```js +const [ signal, slot ] = connect('clicked', 'message') + + ); } @@ -42,14 +43,12 @@ Banner.propTypes = { }; function App () { - const buttonRef = useRef(null); - const bannerRef = useRef(null); - connect(buttonRef, 'clicked', bannerRef, 'message'); + const message = useSlot('clicked'); return ( <> - - - ); +function TextInput () { + const [ value, setValue ] = useState(''); + const emit = useSignal('updated'); + const onChange = (e) => { + setValue(e.target.value); + emit(e.target.value); + }; + return {message} - ); +function Banner({ name }) { + return

Hello, {name}!

; } Banner.propTypes = { - message: PropTypes.string + name: PropTypes.string }; function App () { - const message = useSlot('clicked'); + const name = useSlot('updated'); return ( <> - -