UNPKG

12.9 kBMarkdownView Raw
1Composi
2=======
3
4Contents
5--------
6- [Installation](../README.md)
7- [JSX](./jsx.md)
8- [Hyperx](./hyperx.md)
9- [Hyperscript](./hyperscript.md)
10- Functional Components
11 - [Functional Components](#Functional-Components)
12 - [Virtual Dom](#Virtual-Dom)
13 - [Creating Functional Components](#Creating-Functional-Components)
14 - [Using Mount with Render](#Using-Mount-with-Render)
15 - [List with Map](#List-with-Map)
16 - [Custom Tags](#Custom-Tags)
17 - [Data Flows Down](#Data-Flows-Down)
18 - [Events](#Events)
19 - [handleEvent Interface](#handleEvent-Interface)
20 - [Lifecycle Hooks](#Lifecycle-Hooks)
21 - [Component Class Advantages](#Component-Class-Advantages)
22- [Mount, Render and Unmount](./render.md)
23- [Components](./components.md)
24- [State](./state.md)
25- [Lifecycle Methods](./lifecycle.md)
26- [Events](./events.md)
27- [Styles](./styles.md)
28- [Unmount](./unmount.md)
29- [State Management with DataStore](./data-store.md)
30- [Third Party Libraries](./third-party.md)
31- [Deployment](./deployment.md)
32- [Differrences with React](./composi-react.md)
33
34
35## Functional Components
36
37The component architecture is based on classes. If you favor functional programing over OOP, you might prefer to use functional components. Functional Components are always stateless, so you will want to use them with some kind of state management, such as Redux, Mobx, etc.
38
39## Virtual Dom
40
41When you create components with the Component class, you have a reference for the component's properties, methods, etc. This also provides a point of reference for the virtual DOM. In contrast, functional components do not have this reference for their virtual DOM. Instead, with functional components, the scope in which the function is invoked becomes the scope for the virtual DOM. This means as your project grows and code gets out over many files, the scope of a functional component can be spread across several files. For components this is not a problem. For functional components this means the first time it gets invoked in a new scope, the previous virtual DOM will be replaced by a new one. Unless you are creating a list with 10,000 or more items, you won't notice any inefficencies. However, it does result in more layout thrashing than when creating class-based components.
42
43## Creating Functional Components
44
45Functional components use [JSX](./jsx.md) to create markup, but technically you could also use [Hyperx](./hyperx.md) with ES6 template literals. Here we're going to look at using JSX.
46
47To create a functional component, you start with a function, surprise! The function could accept some data, or not. It depends on what the function needs to return. If it is returning static content, no need for a parameter of data. If you want the function component to consume some data, then pass it in as a parameter. And of course you'll need to return some markup. In the example below we have a simple, function component that creates a header:
48
49```javascript
50// title.js:
51// We need to import the "h" function:
52import {h} from 'composi'
53
54// Define function that takes props for data:
55export function Title(props) {
56 // Return the header with name from props:
57 return (
58 <nav>
59 <h1>Hello, {props.name}!</h1>
60 </nav>
61 )
62}
63```
64
65If we were to do this with the Composi `h` function, it would look like this:
66
67```javascript
68// app.js:
69import {h} from 'composi'
70
71// Define function that takes props for data:
72export function Title(name) {
73 return h(
74 'nav', {}, h(
75 'h1', {}, name
76 )
77 )
78}
79```
80
81Both examples above create virtual nodes, so we will need a way to get them into the DOM. Composi provides the `mount` function for that purpose. It works similar to React's `ReactDOM.render` function, but is specifically for mounting a functional component. It takes two arguments: a tag/vnode and a selector/element in which to insert the markup.
82
83In our `app.js` file we import `h` and `render` and then the `Title` functional component and render it to the DOM:
84
85```javascript
86// app.js:
87import {h, mount} from 'composi'
88import {Title} from './components/title'
89
90// Define data for component:
91const name = 'World'
92
93// Render the component into header tag:
94mount(<Title {...{name}}/>, 'header')
95```
96
97This will convert the functional component into a virtual node (vnode) and then convert that into actual nodes inside the header tag. Because a virtual node was created, we can re-render this later with new data and Composi will patch the DOM efficiently for us. In this case that would involve patching the text node of the `h1` tag.
98
99## Using Mount with Render
100
101The `mount` function returns a reference to the DOM tree that was created. This is useful for when you need to update a component. You can capture that value in a variable and then pass it to Composi's `render` funcion to update a functional component. Notice in the following example how we use `mount` to get a reference to the component and then pass it to the `render` function in a `setTimeout` to update it:
102
103```javascript
104// Remember to import both Mount, Render and Unmount:
105import {h, mount, render} from 'composi'
106function Clock({date}) {
107 return (
108 <div>
109 <h3>The Current Time</h3>
110 <p>It is {date.toLocaleTimeString()}.</p>
111 </div>
112 )
113}
114
115const clock = mount(<Clock date={new Date()} />, 'section')
116
117setInterval(
118 () => {
119 // Pass the clock reference to render function to update it:
120 render(clock, <Clock date={new Date()}/>, 'section')
121 },
122 1000
123)
124```
125
126The `render` function expects three arguments:
127
1281. A reference to the already mounted component.
1292. The tag to use for rendering.
1303. The container in which the component was mounted.
131
132
133## List with Map
134
135Now lets create a functional component that takes an array and outputs a list of items. Notice that we are using [BEM](https://css-tricks.com/bem-101/) notation for class names to make styling easier.
136
137```javascript
138// list.js:
139import {h} from 'composi'
140
141export function List(props) {
142 return (
143 <div class='container--list'>
144 <ul class='list--fruits'>
145 {props.items.map(item => <li class='list--fruits__item'>{props.item}</li>)}
146 </ul>
147 </div>
148 )
149}
150```
151
152This list is consuming an `items` array:
153
154```javascript
155// items.js:
156export const items = ['Apples', 'Oranges', 'Bananas']
157```
158
159In our `app.js` file we put this all together:
160
161```javascript
162// app.js:
163import {h, mount} from 'composi'
164import {Title} from './components/title'
165import {List} from './components/list'
166import {items} from './items'
167
168// Define data for Title component:
169const name = 'World'
170
171// Mount component into header:
172mount(<Title {...{name}}/>, 'header')
173
174// Mount list component into section with items data:
175mount(<List {...{items}}/>, 'section')
176```
177
178## Custom Tags
179
180We can break this list down a bit using a custom tag for the list item. We will need to pass the data down to the child component through its props:
181
182
183```javascript
184// list.js:
185import {h} from 'composi'
186
187function ListItem(props) {
188 return (
189 <li class='list--fruits__item'>{props.item}</li>
190 )
191}
192
193export function List(props) {
194 return (
195 <div class='container--list'>
196 <ul class='list--fruits'>
197 {props.items.map(item => <ListItem {...{item}}/>)}
198 </ul>
199 </div>
200 )
201}
202```
203
204## Data Flows Down
205
206When using custom tags inside a functional component, the data flows down, from the parent to the children. There is no two-way data binding. This makes it easy to reason about. If you need a child component to communicate some kind of data change to its parent, you can use events or any pubsub solution from NPM. We have a tiny and efficient pubsub module on NPM called [pubber](https://www.npmjs.com/package/pubber) that is perfect for these situations.
207
208## Events
209
210What if we wanted to make this list dynamic by allowing the user to add items? For that we need events. We'll add a text input and button so the user can enter an item. Since this doesn't involve any data consuming any data, their function just needs to return the markup for the nodes to be created. We'll call this function component `ListForm`:
211
212```javascript
213import {h} from 'composi'
214
215function ListForm() {
216 return (
217 <p>
218 <input class='list--fruits__input--add' placeholder='Enter Item...' type="text"/>
219 <button class='list--fruits__button--add'>Add Item</button>
220 </p>
221 )
222}
223function ListItem(props) {
224 return (
225 <li>{props.item}</li>
226 )
227}
228
229export function List(props) {
230 return (
231 <div class='container--list'>
232 <ListForm />
233 <ul>
234 {props.items.map(item => <ListItem {...{item}}/>)}
235 </ul>
236 </div>
237 )
238}
239```
240
241## handleEvent Interface
242
243We are going to use the `handleEvent` interface to wire up events for the component. We'll do this in a separate file and import it into our `app.js` file. To use `handleEvent` with a functional component we'll need to create an object with the `handleEvent` function on it. Then we'll use a standar event listener and pass the that object instead of a callback.
244
245Since we want to be able to add a new fruit to the list, we need to have access to the data and also the function component that mounts the list. Therefore we import them into the `events.js` file.
246
247```javascript
248// app.js:
249import {h, mount, render} from 'composi'
250import {items} from './items'
251import {List} from './components/list'
252
253// Mount the component and capture reference in "list" variable:
254const list = mount(<List {...{items}}/>, 'section')
255
256export const events = {
257 addItem(e) {
258 const input = document.querySelector('.list__input--add')
259 const value = input.value
260 if (value) {
261 items.push(value)
262 // Pass list variable to render function to update component:
263 list = render(list, <List {...{items}}/>, 'section')
264 input.value = ''
265 }
266 },
267 handleEvent(e) {
268 e.target.className === 'list__button--add' && this.addItem(e)
269 }
270}
271
272// Attach event listener for list and pass the events object instead of a callback:
273document.querySelector('.container-list').addEventListener('click', events)
274```
275
276Notice that we first mount the list. This gives us a reference to the mounted component. We use this reference in our event handler along with the component's container to update the list in place. This allows Composi to use its virtual DOM to efficiently update the DOM nodes with the least layout thrashing as possible.
277
278And that's it. With that we now have an interactive functional component that updates in real time with a virtual DOM.
279
280## Lifecycle Hooks
281
282Functional components have three lifecycle hooks:
283
2841. onmount
2852. onupdate
2863. onunmount
287
288The are attached to the element where you want to track the lifecycle event. They are added like any other prop.
289
290**onmount** gets passed the element that it is on as its argument. This lets you do things such as registering delegated events, accessing the DOM of the component, etc.
291
292**onupdate** gets passed a reference to the element it is attached to, the old props, and the new props. You can check the difference between the two sets of props to see what changed. This lifecyle hook executes every time you call the `render` function on the functional component. If you have a list of items and you only want `onupdate` to fire when an item's data changes, you can write the functional component so that it memoizes its items.
293
294**onunmount** gets passed a `done` callback and a reference to the element on which it is registered. You can do whatever you want but in the end you need to call `done()` to allow the unmounting to finish.
295
296```javascript
297//... Define removeItem:
298function removeItem(done, el) {
299 //... Do stuff here, like animating `el` before unmounting.
300 // Then call `done` to allow unmounting:
301 done()
302}
303
304// Notice how we pass `done` and `el` to the onupdate function.
305function ListItem({data}) {
306 return (
307 <li key={data.key} onunmount={(done, el) => removeItem(done, el)}>
308 {data.name}
309 </li>
310 )
311}
312```
313
314Since `el` is optional, you only need to pass `done` to the `unmount` callback:
315
316```javascript
317//... Define removeItem:
318function removeItem(done) {
319 //... Do stuff here.
320 // Then call `done` to allow unmounting:
321 done()
322}
323
324// Notice how we pass onely `done` to the onupdate function.
325function ListItem({data}) {
326 return (
327 <li key={data.key} onunmount={(done) => removeItem(done)}>
328 {data.name}
329 </li>
330 )
331}
332```
333
334## Component Class Advantages
335
336Although you can build a complex app using nothing but functional components, there are certain conveniences when using the Component class. First and foremost is the fact that classes allow you to encapsulate functionality using properties and methods defined directly on the component. The second is that the component itself enables more efficient use of the virtual DOM because it keeps track of that internally. When data changes, the component itself initiates its update. No need to call a render function or update method to do that.
337