UNPKG

10.2 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- [Mount and Render](./render.md)
12- [Components](./components.md)
13- [State](./state.md)
14- [Lifecycle Methods](./lifecycle.md)
15- [Events](./events.md)
16- [Styles](./styles.md)
17- [Unmount](./unmount.md)
18- [Third Party Libraries](./third-party.md)
19- [Deployment](./deployment.md)
20
21Functional Components
22---------------------
23The 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.
24
25Virtual Dom
26-----------
27
28When 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.
29
30Creating Functional Components
31------------------------------
32
33Functional 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.
34
35To 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:
36
37```javascript
38// title.js:
39// We need to import the "h" function:
40import {h} from 'composi'
41
42// Define function that takes props for data:
43export function Title(props) {
44 // Return the header with name from props:
45 return (
46 <nav>
47 <h1>Hello, {props.name}!</h1>
48 </nav>
49 )
50}
51```
52
53If we were to do this with the Composi `h` function, it would look like this:
54
55```javascript
56// app.js:
57import {h} from 'composi'
58
59// Define function that takes props for data:
60export function Title(name) {
61 return h(
62 'nav', {}, h(
63 'h1', {}, name
64 )
65 )
66}
67```
68
69Both 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.
70
71In our `app.js` file we import `h` and `render` and then the `Title` functional component and render it to the DOM:
72
73```javascript
74// app.js:
75import {h, mount} from 'composi'
76import {Title} from './components/title'
77
78// Define data for component:
79const name = 'World'
80
81// Render the component into header tag:
82mount(<Title {...{name}}/>, 'header')
83```
84
85This 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.
86
87Using Mount with Render
88-----------------------
89The `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:
90
91```javascript
92// Remember to import both mount and render:
93import {h, mount, render} from 'composi'
94function Clock({date}) {
95 return (
96 <div>
97 <h3>The Current Time</h3>
98 <p>It is {date.toLocaleTimeString()}.</p>
99 </div>
100 )
101}
102
103const clock = mount(<Clock date={new Date()} />, 'section')
104
105setInterval(
106 () => {
107 // Pass the clock reference to render function to update it:
108 render(<Clock date={new Date()}/>, clock)
109 },
110 1000
111)
112```
113
114List with Map
115-------------
116
117Now 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.
118
119```javascript
120// list.js:
121import {h} from 'composi'
122
123export function List(props) {
124 return (
125 <div class='container--list'>
126 <ul class='list--fruits'>
127 {props.items.map(item => <li class='list--fruits__item'>{props.item}</li>)}
128 </ul>
129 </div>
130 )
131}
132```
133
134This list is consuming an `items` array:
135
136```javascript
137// items.js:
138export const items = ['Apples', 'Oranges', 'Bananas']
139```
140
141In our `app.js` file we put this all together:
142
143```javascript
144// app.js:
145import {h, mount} from 'composi'
146import {Title} from './components/title'
147import {List} from './components/list'
148import {items} from './items'
149
150// Define data for Title component:
151const name = 'World'
152
153// Mount component into header:
154mount(<Title {...{name}}/>, 'header')
155
156// Mount list component into section with items data:
157mount(<List {...{items}}/>, 'section')
158```
159
160Custom Tags
161-----------
162
163We 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:
164
165
166```javascript
167// list.js:
168import {h} from 'composi'
169
170function ListItem(props) {
171 return (
172 <li class='list--fruits__item'>{props.item}</li>
173 )
174}
175
176export function List(props) {
177 return (
178 <div class='container--list'>
179 <ul class='list--fruits'>
180 {props.items.map(item => <ListItem {...{item}}/>)}
181 </ul>
182 </div>
183 )
184}
185```
186
187Data Flows Down
188---------------
189
190When 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.
191
192Events
193------
194
195What 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`:
196
197```javascript
198import {h} from 'composi'
199
200function ListForm() {
201 return (
202 <p>
203 <input class='list--fruits__input--add' placeholder='Enter Item...' type="text"/>
204 <button class='list--fruits__button--add'>Add Item</button>
205 </p>
206 )
207}
208function ListItem(props) {
209 return (
210 <li>{props.item}</li>
211 )
212}
213
214export function List(props) {
215 return (
216 <div class='container--list'>
217 <ListForm />
218 <ul>
219 {props.items.map(item => <ListItem {...{item}}/>)}
220 </ul>
221 </div>
222 )
223}
224```
225
226handleEvent Interface
227---------------------
228
229We 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.
230
231Since 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.
232
233```javascript
234// app.js:
235import {h, mount, render} from 'composi'
236import {items} from './items'
237import {List} from './components/list'
238
239// Mount the component and capture reference in "list" variable:
240const list = mount(<List {...{items}}/>, 'section')
241
242export const events = {
243 addItem(e) {
244 const input = document.querySelector('.list__input--add')
245 const value = input.value
246 if (value) {
247 items.push(value)
248 // Pass list variable render function to update component:
249 render(<List {...{items}}/>, list)
250 input.value = ''
251 }
252 },
253 handleEvent(e) {
254 e.target.className === 'list__button--add' && this.addItem(e)
255 }
256}
257
258// Attach event listener for list and pass the events object instead of a callback:
259document.querySelector('.container-list').addEventListener('click', events)
260```
261
262Notice that we first mount the list. This gives us a reference to the mounted component. We use this reference in our event handler 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.
263
264And that's it. With that we now have an interactive functional component that updates in real time with a virtual dom.
265
266Component Class Advantages
267--------------------------
268
269Although 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 the component itself enables more efficient use of the virtual dom because it keeps track of that internally.
270