|
| 1 | +# Preact |
| 2 | + |
| 3 | +[](http://npm.im/preact) |
| 4 | +[](https://travis-ci.org/developit/preact) |
| 5 | + |
| 6 | +> Name stolen, if found return to jason [at] developit [dot] ca |
| 7 | +
|
| 8 | + |
| 9 | +## Overview |
| 10 | + |
| 11 | +Preact is an attempt to recreate the core value proposition of React (or similar libraries like Mithril) using as little code as possible, with first-class support for ES2015. Currently the library is around **3kb** (minified & gzipped). |
| 12 | + |
| 13 | +It retains a large amount of compatibility with React, but only the [ES6 Classes] interface. As one would expect coming from React, Components are simple building blocks for composing a User Interface. |
| 14 | + |
| 15 | + |
| 16 | +## Getting Started |
| 17 | + |
| 18 | +> You don't _have_ to use ES2015 to use Preact... but you should. |
| 19 | +> |
| 20 | +> I'm going to assume you have some sort of ES2015 build set up using babel and/or webpack/browserify/gulp/grunt/etc. If you don't, start with [preact-boilerplate]. |
| 21 | +
|
| 22 | + |
| 23 | +### Import what you need |
| 24 | + |
| 25 | +The `preact` module provides both named and default exports, so you can either import everything under a namespace of your choosing, or just what you need as locals: |
| 26 | + |
| 27 | +##### Default: |
| 28 | + |
| 29 | +```js |
| 30 | +import preact from 'preact'; |
| 31 | + |
| 32 | +// Tell Babel to transform JSX into preact.h() calls: |
| 33 | +/** @jsx preact.h */ |
| 34 | +``` |
| 35 | + |
| 36 | +##### Named: |
| 37 | + |
| 38 | +```js |
| 39 | +import { h, render, Component } from 'preact'; |
| 40 | + |
| 41 | +// Tell Babel to transform JSX into h() calls: |
| 42 | +/** @jsx h */ |
| 43 | +``` |
| 44 | + |
| 45 | +> Named imports work well for highly structured applications, whereas the default import is quick and never needs to be updated when using different parts of the library. |
| 46 | +> |
| 47 | +> Instead of declaring the `@jsx` pragma in your code, it's best to configure it globally in a `.babelrc`: |
| 48 | +> |
| 49 | +> ``` |
| 50 | +> { "jsxPragma": "h" } |
| 51 | +> ``` |
| 52 | +
|
| 53 | +
|
| 54 | +### Rendering JSX |
| 55 | +
|
| 56 | +Out of the box, Preact provides an `h()` function that turns your JSX into Virtual DOM elements _([here's how](http://jasonformat.com/wtf-is-jsx))_. It also provides a `render()` function that creates a DOM tree from that Virtual DOM. |
| 57 | +
|
| 58 | +To render some JSX, just import those two functions and use them like so: |
| 59 | +
|
| 60 | +```js |
| 61 | +import { h, render } from 'preact'; |
| 62 | +
|
| 63 | +render(( |
| 64 | + <div id="foo"> |
| 65 | + <span>Hello, world!</span> |
| 66 | + <button onClick={ e => alert("hi!"); }>Click Me</button> |
| 67 | + </div> |
| 68 | +), document.body); |
| 69 | +``` |
| 70 | +
|
| 71 | +This should seem pretty straightforward if you've used [hyperscript] or one of its many friends. |
| 72 | + |
| 73 | +Rendering hyperscript with a virtual DOM is pointless, though. We want to render components and have them updated when data changes - that's where the power of virtual DOM diffing shines. |
| 74 | + |
| 75 | + |
| 76 | +### Components |
| 77 | + |
| 78 | +Preact exports a generic `Component` class, which can be extended to build encapsulated, self-updating pieces of a User Interface. Components support all of the standard React [lifecycle methods], like `shouldComponentUpdate()` and `componentWillReceiveProps()`. Providing specific implementations of these methods is the preferred mechanism for controlling _when_ and _how_ components update. |
| 79 | + |
| 80 | +Components also have a `render()` method, but unlike React this method is passed `(props, state)` as arguments. This provides an ergonomic means to destructure `props` and `state` into local variables to be referenced from JSX. |
| 81 | + |
| 82 | +Let's take a look at a very simple `Clock` component, which shows the current time. |
| 83 | + |
| 84 | +```js |
| 85 | +import { h, render, Component } from 'preact'; |
| 86 | + |
| 87 | +class Clock extends Component { |
| 88 | + render() { |
| 89 | + let time = new Date().toLocaleTimeString(); |
| 90 | + return <span>{ time }</span>; |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +// render an instance of Clock into <body>: |
| 95 | +render(<Clock />, document.body); |
| 96 | +``` |
| 97 | + |
| 98 | + |
| 99 | +That's great. Running this produces the following HTML DOM structure: |
| 100 | + |
| 101 | +```html |
| 102 | +<span>10:28:57 PM</span> |
| 103 | +``` |
| 104 | + |
| 105 | +In order to have the clock's time update every second, we need to know when `<Clock>` gets mounted to the DOM. _If you've used HTML5 Custom Elements, this is similar to the `attachedCallback` and `detachedCallback` lifecycle methods._ Preact invokes the following lifecycle methods if they are defined for a Component: |
| 106 | + |
| 107 | +| Lifecycle method | When it gets called | |
| 108 | +|-----------------------------|--------------------------------------------------| |
| 109 | +| `componentWillMount` | before the component gets mounted to the DOM | |
| 110 | +| `componentDidMount` | after the component gets mounted to the DOM | |
| 111 | +| `componentWillUnmount` | prior to removal from the DOM | |
| 112 | +| `componentDidUnmount` | after removal from the DOM | |
| 113 | +| `componentWillReceiveProps` | before new props get accepted | |
| 114 | +| `shouldComponentUpdate` | before `render()`. Return `false` to skip render | |
| 115 | +| `componentWillUpdate` | before `render()` | |
| 116 | +| `componentDidUpdate` | after `render()` | |
| 117 | + |
| 118 | + |
| 119 | + |
| 120 | +So, we want to have a 1-second timer start once the Component gets added to the DOM, and stop if it is removed. We'll create the timer and store a reference to it in `componentDidMount`, and stop the timer in `componentWillUnmount`. On each timer tick, we'll update the component's `state` object with a new time value. Doing this will automatically re-render the component. |
| 121 | + |
| 122 | +```js |
| 123 | +import { h, render, Component } from 'preact'; |
| 124 | + |
| 125 | +class Clock extends Component { |
| 126 | + constructor() { |
| 127 | + super(); |
| 128 | + // set initial time: |
| 129 | + this.state.time = Date.now(); |
| 130 | + } |
| 131 | + |
| 132 | + componentDidMount() { |
| 133 | + // update time every second |
| 134 | + this.timer = setInterval(() => { |
| 135 | + this.setState({ time: Date.now() }); |
| 136 | + }, 1000); |
| 137 | + } |
| 138 | + |
| 139 | + componentWillUnmount() { |
| 140 | + // stop when not renderable |
| 141 | + clearInterval(this.timer); |
| 142 | + } |
| 143 | + |
| 144 | + render(props, state) { |
| 145 | + let time = new Date(state.time).toLocaleTimeString(); |
| 146 | + return <span>{ time }</span>; |
| 147 | + } |
| 148 | +} |
| 149 | + |
| 150 | +// render an instance of Clock into <body>: |
| 151 | +render(<Clock />, document.body); |
| 152 | +``` |
| 153 | + |
| 154 | +Now we have a ticking clock! |
| 155 | + |
| 156 | + |
| 157 | +### Props & State |
| 158 | + |
| 159 | +The concept (and nomenclature) for `props` and `state` is the same as in React. `props` are passed to a component by defining attributes in JSX, `state` is internal state. Changing either triggers a re-render, though by default Preact re-renders Components asynchronously for `state` changes and synchronously for `props` changes. You can tell Preact to render `prop` changes asynchronously by setting `options.syncComponentUpdates` to `false`. |
| 160 | + |
| 161 | + |
| 162 | +## Examples |
| 163 | + |
| 164 | +Here is a somewhat verbose Preact `<Link>` component: |
| 165 | + |
| 166 | +```js |
| 167 | +class Link extends Component { |
| 168 | + render(props, state) { |
| 169 | + return <a href={ props.href }>{ props.children }</a>; |
| 170 | + } |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +Since this is ES6/ES2015, we can further simplify: |
| 175 | + |
| 176 | +```js |
| 177 | +class Link extends Component { |
| 178 | + render({ href, children }) { |
| 179 | + return <a {...{ href, children }} />; |
| 180 | + } |
| 181 | +} |
| 182 | + |
| 183 | +// or, for wide-open props support: |
| 184 | +class Link extends Component { |
| 185 | + render(props) { |
| 186 | + return <a {...props} />; |
| 187 | + } |
| 188 | +} |
| 189 | +``` |
| 190 | + |
| 191 | + |
| 192 | +## Extensions |
| 193 | + |
| 194 | +It is likely that some projects based on Preact would wish to extend Component with great new functionality. |
| 195 | + |
| 196 | +Perhaps automatic connection to stores for a Flux-like architecture, or mixed-in context bindings to make it feel more like `React.createClass()`. Just use ES2015 inheritance: |
| 197 | + |
| 198 | +```js |
| 199 | +class BoundComponent extends Component { |
| 200 | + constructor(props) { |
| 201 | + super(props); |
| 202 | + this.bind(); |
| 203 | + } |
| 204 | + bind() { |
| 205 | + this.binds = {}; |
| 206 | + for (let i in this) { |
| 207 | + this.binds[i] = this[i].bind(this); |
| 208 | + } |
| 209 | + } |
| 210 | +} |
| 211 | + |
| 212 | +// example usage |
| 213 | +class Link extends BoundComponent { |
| 214 | + click() { |
| 215 | + open(this.href); |
| 216 | + } |
| 217 | + render() { |
| 218 | + let { click } = this.binds; |
| 219 | + return <span onclick={ click }>{ children }</span>; |
| 220 | + } |
| 221 | +} |
| 222 | +``` |
| 223 | + |
| 224 | + |
| 225 | +The possibilities are pretty endless here. You could even add support for rudimentary mixins: |
| 226 | + |
| 227 | +```js |
| 228 | +class MixedComponent extends Component { |
| 229 | + constructor() { |
| 230 | + super(); |
| 231 | + (this.mixins || []).forEach( m => Object.assign(this, m) ); |
| 232 | + } |
| 233 | +} |
| 234 | +``` |
| 235 | + |
| 236 | + |
| 237 | +## License |
| 238 | + |
| 239 | +MIT |
| 240 | + |
| 241 | + |
| 242 | + |
| 243 | +[ES6 Classes]: https://facebook.github.io/react/docs/reusable-components.html#es6-classes |
| 244 | +[hyperscript]: https://github.com/dominictarr/hyperscript |
| 245 | +[preact-boilerplate]: https://github.com/developit/preact-boilerplate |
| 246 | +[lifecycle methods]: https://facebook.github.io/react/docs/component-specs.html |
0 commit comments