GROWTH LOG STORIES

함께 만든 성장을,
기록으로 연결합니다.

AI와 함께 배우고, 직접 만들고, 서로 나눈
그로스로그 멤버들의 생생한 성장 기록을 만나보세요.

성장 기록
5기+이어진 커뮤니티
HEALTHIO
PROJECT

사진 한 장으로 시작하는 건강 루틴, Health IO

대표 이야기 읽기

ALL STORIES

성장의 순간을
한곳에서 만나보세요.

프로젝트의 시작부터 모임에서 나눈 인사이트까지,
그로스로그가 함께 만든 기록을 모았습니다.
개의 성장 기록

성장일지

[GL 4기 8회차 ②] 미리 바꿔두면 나중이 싸집니다 — useMemo 메모이제이션, 파이썬 nonlocal과 클로저, 벡터의 성분 표현 🌱

GROWTH LOG🌱 2026. 8. 11. 00:12
GL 4기 성장일지 8회차 큐레이션

안녕하세요! 그로스로그 입니다!😊 🌱

계산 결과를 저장해뒀다 다시 쓰는 훅, 함수가 끝나도 값이 살아남는 파이썬 문법, 그리고 기하 문제를 덧셈과 곱셈으로 바꾸는 벡터 표기. 4기 8회차 세 편입니다.

4기 8회차는 2026년 1월입니다. 세 편이 한 번 다른 형태로 바꿔두면 나중 계산이 싸진다는 이야기를 각자의 자리에서 하고 있어요.

 

01

🧠 React Hook — useMemo

박현구 님

이런 내용이에요
계산 결과를 렌더링 사이에 저장해두는 훅을 정리한 글입니다.
매개변수 둘과 반환값을 문서 수준으로 짚고, 주의 사항 세 가지를 그대로 옮겨왔어요.
특히 중요한 한 줄이 있습니다. "React는 캐싱된 값을 버려야 할 특별한 이유가 없는 한 버리지 않는다" — 뒤집으면 버릴 수도 있다는 뜻입니다.
시작 부분에 컴파일러가 이걸 자동으로 해준다는 공식 안내도 인용해뒀습니다.

6·7회차에 이어 만나는 분입니다. 8·9·10회차는 프런트엔드 쪽으로 옮겨 왔어요.

이 글의 성격은 문서를 정독한 기록입니다. 그런데 정독의 값어치가 주의 사항 부분에서 나옵니다. 보통 훑고 지나가는 자리거든요.

조금 더 들어가 보면

🔁 리액트가 계속 다시 계산하는 이유

이 훅이 왜 필요한지 알려면 리액트의 기본 동작을 봐야 합니다.

리액트에서 컴포넌트 함수는 화면을 그릴 때마다 처음부터 다시 실행됩니다. 상태가 바뀌든, 부모가 다시 그려지든, 함수 본문 전체가 다시 돕니다.

그래서 함수 안에 이런 게 있으면 매번 다시 계산돼요.

· 목록 만 건을 걸러내는 코드

· 큰 배열을 정렬하는 코드

· 무거운 문자열 처리

입력이 하나도 안 바뀌었는데도 다시 합니다. 리액트는 "바뀌었는지"를 알 방법이 없으니까요.

useMemo가 하는 일은 그 판단을 사람이 대신 해주는 것입니다. "이 값들이 안 바뀌면 계산도 안 바뀐다" 고 알려주는 것이죠. 그러면 리액트가 이전 결과를 그대로 돌려줍니다.

📋 의존성 목록이 곧 계약입니다

두 번째 매개변수인 의존성 목록이 이 훅의 전부입니다. 그리고 여기서 대부분의 버그가 납니다.

저자가 옮긴 설명이 정확해요.

"calculateValue 코드 내에서 참조된 모든 반응형 값들의 목록입니다."

"모든" 이 중요합니다. 계산 안에서 쓰는 값 중 하나라도 목록에서 빠지면, 그 값이 바뀌어도 리액트는 모릅니다. 옛날 결과를 계속 돌려줘요.

증상이 고약합니다. 에러가 안 나거든요. 그냥 화면에 옛날 값이 남아 있습니다. 그리고 다른 이유로 화면이 다시 그려지는 순간 갑자기 최신 값으로 바뀝니다. 재현이 안 되는 버그의 전형적인 형태예요.

그래서 저자가 린터 이야기를 함께 옮긴 게 실용적입니다. 사람이 목록을 손으로 관리하면 반드시 빠뜨리는데, 도구가 검사해주면 대부분 막힙니다.

비교 방식도 알아둘 만합니다. 리액트는 의존성을 값 비교가 아니라 같은 것인지로 봅니다. 그래서 객체나 배열을 의존성에 넣으면, 내용이 같아도 매번 새로 만들어진 것이면 다르다고 판정해요. 메모이제이션을 걸었는데 매번 다시 계산되는 흔한 원인입니다.

🗑️ "버릴 수도 있다"는 한 줄

이 글에서 가장 값어치 있는 대목이 여기입니다.

"React는 캐싱된 값을 버려야 할 특별한 이유가 없는 한 버리지 않습니다."

읽는 방향을 뒤집어야 합니다. 버릴 수도 있다는 뜻이거든요. 실제로 문서는 앞으로 더 많은 상황에서 버릴 수 있다고 예고합니다.

그래서 결론이 나옵니다. useMemo를 정확성에 의존하면 안 됩니다.

· 성능 최적화용 — 다시 계산돼도 결과가 같으니 느려질 뿐입니다. 괜찮습니다

· 값을 보관하는 용도 — 버려지면 값이 사라집니다. 안 됩니다

"이 값이 유지돼야 로직이 맞는다" 면 그건 다른 도구를 써야 합니다. 저자가 옮긴 문서에도 상태 변수나 참조를 쓰라고 안내돼 있어요.

이 구분이 캐시 일반의 원칙입니다. 캐시는 없어도 결과가 같아야 하고, 없으면 틀리는 순간 그건 캐시가 아니라 저장소입니다.

⚠️ 개발 모드에서 두 번 도는 이유

주의 사항 중 계산 함수가 두 번 호출된다는 항목도 짚어둘 만합니다. 일부러 그러는 것입니다.

리액트는 이 함수가 순수하다고 전제합니다. 같은 입력에 같은 출력을 내고, 다른 데 영향을 안 주는 함수요. 그런데 실수로 안에서 무언가를 바꾸는 코드를 넣으면 나중에 이상한 버그가 됩니다.

그래서 개발 중에는 일부러 두 번 불러서 두 번 부른 결과가 달라지면 티가 나게 합니다. 안에서 카운터를 올리거나 배열을 직접 고치고 있었다면 여기서 드러나요.

"제약을 지켰는지 확인하려고 일부러 두 번 한다." 배포 환경에는 영향이 없고요. 이런 장치가 있다는 걸 모르면 "왜 두 번 찍히지" 하고 시간을 쓰게 됩니다.

🤖 컴파일러가 대신해준다는 안내

저자가 글 앞부분에 인용한 문장도 눈에 띕니다. 리액트 컴파일러가 값과 함수를 자동으로 기억해주니 손으로 부르는 일이 줄어든다는 것이요.

도구를 배우는 글에 "이건 곧 자동화된다"는 안내를 같이 붙인 것이 정직합니다. 그렇다고 배울 필요가 없어지는 건 아니에요. 자동으로 되는 것도 결국 같은 판단을 대신하는 것이라, 왜 필요한지를 알아야 자동화가 잘못 동작할 때 알아챌 수 있습니다.

3회차에서 봤던 결이 여기서도 나옵니다. 넘기더라도 무엇을 넘겼는지는 알고 있어야 한다는 것이요.

🚫 안 쓰는 게 나은 경우

이 훅을 배우면 흔히 하는 실수가 있습니다. 일단 다 감싸는 것이요.

그런데 이것도 공짜가 아닙니다. 매번 의존성 목록을 만들고, 이전 것과 하나씩 비교하고, 결과를 들고 있어야 하거든요. 가벼운 계산이라면 그 관리 비용이 계산 비용보다 큽니다.

기준을 세우면 이렇습니다.

· 감쌀 만한 것 — 큰 목록을 걸러내거나 정렬하는 계산, 결과가 다시 아래로 내려가면서 하위 컴포넌트의 렌더링을 좌우하는 값

· 감쌀 필요 없는 것 — 숫자 몇 개를 더하거나 문자열을 잇는 정도

4회차에서 본 시간 복잡도 이야기와 같은 감각입니다. 재보고 나서 고르는 것이요. 실제로 리액트 문서도 "먼저 측정해보라"고 안내합니다.

그리고 감싸지 않았을 때의 대안이 따로 있기도 합니다. 애초에 그 계산이 컴포넌트 안에 있을 이유가 없으면 밖으로 빼면 되고, 목록을 걸러내는 일이 매번 필요 없으면 상태 구조를 바꾸는 게 나을 수도 있어요. 최적화 훅은 구조를 못 바꿀 때 쓰는 도구입니다.

🧵 계산을 아끼는 것과 다시 그리는 것을 아끼는 것

이 훅을 이야기할 때 자주 섞이는 두 가지를 나눠두겠습니다. 무엇을 아끼려는 것인가요.

· 계산을 아끼기 — 무거운 함수를 다시 안 부르는 것. 이 훅의 원래 목적입니다

· 다시 그리기를 아끼기 — 아래쪽 컴포넌트가 다시 그려지지 않게 하는 것

둘은 다른 문제인데, 실무에서는 두 번째 때문에 이 훅을 쓰는 경우가 더 많습니다.

리액트에서 부모가 다시 그려지면 자식도 기본적으로 다시 그려집니다. 그걸 막는 장치가 따로 있는데, 그 장치는 넘겨받은 값이 이전과 같은지로 판단합니다.

그래서 부모가 매번 새 객체나 새 함수를 만들어 넘기면, 내용이 같아도 다르다고 보고 자식이 계속 다시 그려져요. 이때 넘기는 값을 이 훅으로 감싸두면 같은 것이 유지되어 자식이 멈춥니다.

계산 자체는 하나도 안 무거운데 감싸는 이유가 여기 있습니다. "계산을 아끼려고"가 아니라 "아래쪽을 멈추려고"요.

그리고 이 경우에는 자식 쪽에도 장치가 걸려 있어야 효과가 납니다. 위에서만 감싸두면 아무 일도 안 일어나요. 이 짝을 모르고 위쪽만 감싸는 게 흔한 헛수고입니다.

앞에서 이야기한 "재보고 나서 고른다"가 여기서도 필요합니다. 무엇 때문에 느린지 먼저 알아야 어느 쪽 장치를 걸지 정해집니다.

그리고 재는 방법도 도구가 있습니다. 어떤 컴포넌트가 왜 다시 그려졌는지 기록해주는 개발 도구가 있어서, 켜두고 화면을 조작해보면 불필요하게 도는 자리가 목록으로 나옵니다. 감으로 짐작해 감싸는 것보다 훨씬 빠르고, 감쌌는데 효과가 없는 헛수고도 막아줍니다. 저자가 문서를 정독한 김에 이 도구까지 같이 익혀두면 좋은 자리예요.

🧊 그리고 대개는 다시 그려도 괜찮습니다

마지막으로 균형을 하나 잡아두겠습니다. 리액트의 다시 그리기는 생각보다 쌉니다.

화면을 다시 그린다고 해서 브라우저가 실제로 화면 전체를 다시 칠하는 게 아니거든요. 리액트가 이전 결과와 비교해서 달라진 부분만 실제 화면에 반영합니다. 그래서 컴포넌트 함수가 다시 돌아도 눈에 보이는 작업은 거의 안 일어나는 경우가 많아요.

그래서 순서가 이렇게 됩니다.

1. 느린지 먼저 확인합니다 — 실제로 버벅이지 않으면 손대지 않는 게 맞습니다

2. 무엇 때문인지 봅니다 — 계산이 무거운 건지, 아래쪽이 과하게 도는 건지

3. 그 자리에만 겁니다 — 전부 감싸면 관리할 것만 늘어납니다

"최적화는 문제가 확인된 다음에" 라는 오래된 원칙이 여기서도 그대로입니다. 그리고 앞에서 본 컴파일러 이야기가 이 부담을 더 줄여주는 방향이고요.

📝 원문에서는 이렇게 씁니다

가장 기본적인 사용을 그대로 옮겨왔습니다.

import { useMemo } from 'react';

function TodoList({ todos, tab, theme }) {
  const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  // ...
}

의존성에 theme가 없는 게 핵심입니다. 테마가 바뀌어도 걸러낸 목록은 안 바뀌니까요. 그래서 테마만 바뀐 렌더링에서는 필터링이 다시 안 돕니다.

"무엇이 바뀌면 다시 계산해야 하는가"를 사람이 적어주는 것. 이 한 줄이 그걸 보여줍니다.

큐레이터 노트

주의 사항까지 정독한 기록이라 뽑았습니다. 사용법은 어디에나 있는데, "캐싱된 값을 버릴 수도 있다"와 "개발 모드에서 두 번 호출한다"를 짚어 옮긴 글은 덜 흔해요.

특히 버릴 수도 있다는 항목이 중요합니다. 이걸 알면 이 훅을 성능용으로만 쓰게 되고, 모르면 값 보관용으로 쓰다가 재현 안 되는 버그를 만납니다. 도구의 보장 범위를 정확히 아는 것이 이 기록의 값어치예요.

10회차에서 같은 분의 다른 훅 정리를 다시 만납니다.

원문 읽으러 가기  ↗medium.com/@hyungu1215/react-hook-usememo-34a3bda97dc6
 

02

🪆 파이썬 스터디 - nonlocal

노현주 님

이런 내용이에요
중첩 함수에서 상위 함수의 변수를 고치겠다고 선언하는 키워드를 정리한 글입니다.
정의가 세 줄로 정확합니다. 중첩 함수 안에서만 유효하고, 전역이 아닌 상위 스코프를 찾고, 선언한 변수를 고치면 실제 상위 함수의 값이 바뀐다고요.
카운터 예제로 보여주고, 왜 쓰는지를 셋으로 정리합니다. 객체지향 · 클로저 · 캡슐화요.
그리고 실제 프레임워크 내부에서 어떻게 쓰이는지까지 찾아봤습니다.

4·6·7회차에 이어 네 번째로 만나는 분입니다. 이번엔 스터디 기록이에요.

이 글에서 눈에 띄는 건 마지막 부분입니다. 문법을 정리하고 끝내지 않고 실제 프레임워크 소스에서 이 키워드가 쓰이는 자리를 찾아봤어요. 7회차의 소스 분석과 같은 습관입니다.

조금 더 들어가 보면

📦 왜 선언이 필요한가

이 키워드가 왜 있어야 하는지부터 짚겠습니다. 파이썬이 대입을 만나면 그 변수를 지역 변수로 취급하기 때문입니다.

함수 안에서 count += 1 이라고 쓰면, 파이썬은 count를 그 함수의 지역 변수로 봅니다. 그런데 오른쪽에서 값을 읽어야 하는데 아직 아무것도 대입되지 않았으니 오류가 나요. 바깥에 같은 이름이 있어도 안 봅니다.

읽기만 할 때는 바깥을 찾아갑니다. 그래서 읽는 건 되는데 고치는 건 안 되는 헷갈리는 상황이 생깁니다.

nonlocal은 이 규칙을 명시적으로 끄는 선언입니다. "이 이름은 지역 변수가 아니라 바깥 것" 이라고 알려주는 것이죠.

전역 변수용 선언과 다른 점도 분명합니다. 전역은 모듈 전체에서 보이는 이름이고, 이건 바로 위 함수의 이름입니다. 2회차에서 이야기한 이름 공간 문제와 같은 자리인데, 여기서는 어느 방의 이름을 고칠 것인지를 지정하는 셈이에요.

🔒 함수가 끝났는데 값이 남습니다

저자가 든 카운터 예제가 이 개념의 핵심을 보여줍니다.

def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

바깥 함수는 안쪽 함수를 만들어 돌려주고 끝납니다. 보통이라면 그 함수의 지역 변수도 같이 사라져야 해요.

그런데 안 사라집니다. 돌려받은 함수를 부를 때마다 값이 1씩 늘어납니다.

저자가 적은 문장이 정확합니다.

"count 변수는 make_counter 호출이 끝난 뒤에도 메모리에 남아 my_counter를 호출할 때마다 값이 갱신된다."

안쪽 함수가 그 변수를 계속 붙들고 있기 때문입니다. 함수가 자기가 만들어진 환경을 데리고 다니는 것, 이걸 클로저라고 부릅니다.

🏛️ 클로저와 객체는 같은 것의 두 얼굴입니다

저자가 사용 이유로 객체지향캡슐화를 꼽은 게 흥미롭습니다. 문법 이야기를 하다가 설계 이야기로 넘어간 것이거든요.

그리고 이건 정확한 연결입니다. 클로저와 객체는 하는 일이 같습니다.

  클로저 객체
상태를 두는 곳 바깥 함수의 지역 변수 인스턴스 속성
그 상태를 쓰는 것 안쪽 함수 메서드
밖에서 직접 접근 안 됩니다 규칙으로만 막습니다

세 번째 줄이 저자가 말한 캡슐화입니다. 카운터 예제에서 바깥에서는 count에 손댈 방법이 없어요. 오직 돌려받은 함수를 통해서만 값이 바뀝니다.

파이썬의 클래스는 사실 이름 앞에 밑줄을 붙이는 관례로 "건드리지 말라"고 표시할 뿐 실제로 막지는 않습니다. 클로저 쪽이 오히려 더 확실하게 감춥니다.

🚩 실제 프레임워크에서 쓰이는 자리

이 글이 문법 정리에서 한 걸음 더 나간 게 여기입니다. 프레임워크 내부에서 이 키워드가 플래그 용도로 쓰인다고 찾아냈어요.

예제가 이렇습니다. 응답을 내보내는 함수 안에서 "응답이 이미 시작됐는가" 를 기록해두는 변수요.

async def some_asgi_app(scope, receive, send):
    response_started = False
    async def sender(message):
        nonlocal response_started
        if message["type"] == "http.response.start":
            response_started = True
        await send(message)

이게 왜 필요한지가 실무적입니다. 응답을 이미 보내기 시작한 뒤에 오류가 나면, 그때는 오류 페이지를 보낼 수가 없습니다. 이미 헤더가 나갔으니까요. 그래서 "시작했는가"를 기록해두고, 오류 처리에서 그 값을 보고 다르게 대응합니다.

같은 형태가 여러 곳에서 나옵니다. 연결이 끊겼는지, 시간이 초과됐는지 같은 표시를 안쪽 함수가 켜두면 바깥에서 읽는 구조요.

"돌려주는 값 말고 다른 경로로 상태를 알려야 할 때" 이 문법이 쓰인다는 것이죠. 문법을 배우는 것과 어디에 쓰이는지 확인하는 것의 차이가 이 부분에 있습니다.

⏰ 클로저가 붙드는 건 값이 아니라 변수입니다

이 개념에서 가장 많이 걸리는 함정을 하나 짚어두겠습니다. 클로저는 그때의 값을 복사해두는 게 아니라, 변수 자체를 붙들고 있습니다.

차이가 언제 드러나느냐면, 반복문 안에서 함수를 여러 개 만들 때입니다.

반복문을 돌면서 함수를 세 개 만들어 목록에 담아뒀다고 해봅시다. 각 함수가 반복 변수를 쓰고 있고요. 나중에 세 함수를 차례로 부르면 0, 1, 2가 나올 것 같은데, 셋 다 마지막 값이 나옵니다.

세 함수가 같은 변수 하나를 보고 있었기 때문입니다. 함수를 만들 당시의 값이 아니라, 부르는 시점의 값을 읽어요.

이건 파이썬만의 문제가 아니라 클로저가 있는 언어에서 반복해 나타나는 함정입니다. 자바스크립트에서 var로 반복 변수를 선언하면 똑같은 일이 벌어지고, 그래서 블록 범위 변수가 도입된 이유 중 하나이기도 해요.

해결은 값을 그 자리에서 고정하는 것입니다. 기본 인자로 받아 묶어두거나, 함수를 만들어주는 함수를 한 겹 더 두는 방식이요. 저자가 든 카운터 예제가 사실 그 구조입니다. 바깥 함수를 부를 때마다 새 변수가 생기니까, 카운터를 두 개 만들면 각각 따로 셉니다.

"붙들고 있는 게 값인가 변수인가." 이 질문을 한 번 통과하면 클로저 관련 버그의 절반이 정리됩니다.

🧹 붙들고 있으면 안 없어집니다

편의의 반대편도 짚어두겠습니다. 클로저가 변수를 붙들고 있으면 그 변수는 정리가 안 됩니다.

보통 함수가 끝나면 그 안의 값들은 치워집니다. 그런데 안쪽 함수가 살아 있는 한 바깥 변수도 같이 살아 있어야 해요. 그래서 안쪽 함수를 어딘가에 계속 들고 있으면 바깥 값도 계속 메모리에 남습니다.

대개는 문제가 안 됩니다. 카운터 하나 정도는 아무것도 아니니까요. 그런데 이런 경우에 커집니다.

· 큰 데이터를 잡고 있는 클로저 — 목록 전체를 참조하는 함수를 어딘가에 등록해두면 그 목록이 안 없어집니다

· 등록해두고 안 지우는 콜백 — 이벤트 처리 함수를 붙여두고 화면이 사라질 때 안 떼면, 그 함수와 그 함수가 붙든 것들이 계속 남습니다

두 번째가 실무에서 메모리 누수의 흔한 원인입니다. 붙일 때는 다들 붙이는데 뗄 때 잊거든요.

그래서 콜백을 등록하는 자리에는 뗄 방법도 같이 만들어두는 게 원칙입니다. 리액트에서 정리 함수를 돌려주게 돼 있는 것도 같은 이유고요.

"함수가 끝나도 값이 남는다"는 이 글의 핵심 문장이, 뒤집으면 "안 없어진다"이기도 합니다. 편의와 부담이 같은 성질에서 나옵니다.

그래서 클로저를 쓸 때 한 번 물어보면 좋은 게 있습니다. 이 함수를 누가 얼마나 오래 들고 있을 것인가요. 잠깐 쓰고 버릴 거면 신경 안 써도 되고, 어딘가에 등록해 오래 살 거면 그 안에서 붙드는 것을 최소로 줄이는 게 맞습니다. 목록 전체가 필요 없고 개수만 필요하다면 개수만 꺼내 붙들면 되니까요. 붙드는 범위를 좁히는 것만으로 대부분 정리됩니다.

📝 원문에서는 이렇게 정리합니다

정의를 세 줄로 압축했습니다.

"중첩 함수 내에서만 유효하다."
"전역이 아닌 상위 스코프를 탐색한다."
"실제 변수 바인딩: nonlocal로 선언된 변수를 수정하면 실제 상위 함수의 변수 값이 바뀐다."

세 번째 줄에 "실제"라는 단어를 두 번 쓴 게 의도적으로 보입니다. 복사본이 아니라 원본이 바뀐다는 것이 이 키워드의 전부거든요.

큐레이터 노트

문법에서 멈추지 않고 실제 쓰이는 자리까지 찾아본 기록이라 뽑았습니다. 스터디에서 배운 것을 정리하고, 그게 프레임워크 안에서 어떤 문제를 푸는 데 쓰이는지 확인했어요.

그리고 문법 이야기를 캡슐화까지 끌고 간 것이 좋았습니다. 클로저가 왜 객체와 비슷한 일을 하는지가 카운터 예제 하나에 다 들어 있고요. 7회차의 소스 분석과 같은 습관이 이어집니다.

원문 읽으러 가기  ↗anyway03.tistory.com/9
 

03

🧭 선형대수 7장 연습문제(1) 풀이

최태형 님

이런 내용이에요
연재의 여덟 번째입니다. 이번 장의 주제는 벡터예요.
문제 구성이 이렇습니다. 성분 계산, 직선의 벡터방정식, 내적, 두 벡터의 사이각.
앞 장들이 행렬이었다면, 여기서 기하로 넘어갑니다.

4장 역행렬, 5장 행렬식에 이어 7장 벡터입니다. 그리고 이번 장에서 성격이 한 번 바뀌어요. 숫자 배열을 다루던 데서 방향과 길이를 다루는 데로 옮겨 갑니다.

조금 더 들어가 보면

➡️ 성분으로 적어두면 기하가 산술이 됩니다

이 장의 가장 큰 이득이 첫 문제부터 나옵니다. 벡터를 성분으로 표시하는 것이요.

화살표로 그린 두 벡터를 더하려면 평행사변형을 그려야 합니다. 자와 각도기가 필요하고, 정확도도 그림 실력에 달려 있어요.

그런데 성분으로 적어두면 각 자리 숫자를 더하기만 하면 됩니다. 저자가 푼 첫 문제가 정확히 그 계산이에요. 벡터 두 개를 여러 배 늘리고 더하고 빼는 식인데, 성분으로 두면 그냥 산수입니다.

기하 문제를 산술 문제로 바꿔두는 것. 이게 이번 회차의 주제와 같습니다. 한 번 다른 형태로 옮겨두면 그 뒤의 모든 계산이 싸집니다.

📏 사이각을 각도기 없이 구하기

내적이 나오는 이유가 여기 있습니다. 저자가 푼 문제 중에 두 벡터의 사이각을 구하라는 게 있어요.

각도를 재려면 원래 각도기가 필요합니다. 그런데 내적을 쓰면 곱하고 더한 값 하나로 각도가 나옵니다.

내적은 두 벡터의 성분을 짝지어 곱해서 더하기만 하면 됩니다. 그런데 이 값이 두 벡터의 길이와 사이각의 코사인을 곱한 것과 같아요. 그래서 길이로 나누면 코사인 값이 남고, 거기서 각도가 나옵니다.

여기서 실용적인 부분이 있습니다. 각도를 굳이 안 구해도 되는 경우가 많아요.

· 내적이 0이면 — 수직입니다. 각도 계산 없이 바로 압니다

· 내적이 양수면 — 같은 방향 쪽입니다

· 내적이 음수면 — 반대 방향 쪽입니다

저자가 푼 문제 중에 "수직임을 보여라" 가 있는데, 그게 이 성질을 쓰는 문제입니다. 곱하고 더해서 0이 나오면 끝이에요.

그리고 이 계산은 코드에서 아주 자주 쓰입니다. 두 방향이 비슷한지 판정하는 일 전부가 이 계산입니다. 문서 검색에서 문장 두 개가 비슷한지 보는 것도, 추천에서 취향이 비슷한지 보는 것도 결국 벡터 사이각을 재는 일이에요. 4기 1차 5회차의 「선형대수와 AI」가 다룬 자리이기도 합니다.

📍 벡터방정식이 편한 이유

직선의 방정식을 벡터로 적는 문제도 여럿 나옵니다. 이게 왜 필요한지 짚어두면 좋습니다.

평면에서 직선을 적을 때는 익숙한 형태가 있습니다. 기울기와 절편으로 적는 것이요. 그런데 이 방식에는 문제가 있어요. 수직선을 못 적습니다. 기울기가 무한대가 되니까요.

그리고 3차원으로 가면 아예 안 됩니다. 3차원에서 하나의 식은 직선이 아니라 평면을 나타내거든요.

벡터방정식은 이 문제들이 없습니다. 한 점을 잡고, 방향을 하나 정하고, 그 방향으로 얼마만큼 갔는지로 적으면 끝입니다. 차원과 무관하게 같은 형태예요.

5·6회차에서 반복해 나온 구도가 여기서도 나옵니다. 특수한 경우에 편한 표기와, 일반적으로 쓸 수 있는 표기요. 그리고 컴퓨터에서 쓰는 건 대개 후자입니다. 예외 처리를 안 해도 되니까요.

🔗 행렬 다음에 벡터가 오는 순서

교재의 순서를 두고 한 가지 짚어둘 만한 게 있습니다. 행렬을 먼저 하고 벡터를 나중에 한다는 것이요.

직관적으로는 반대가 맞아 보입니다. 벡터가 더 간단해 보이니까요. 그런데 이 교재는 행렬과 행렬식을 먼저 다루고 7장에서 벡터로 옵니다.

이유가 있습니다. 앞 장들이 계산 도구를 만들어두는 과정이었기 때문이에요.

· 4장에서 연립방정식을 푸는 절차를 익혔습니다

· 5장에서 되돌릴 수 있는지 판정하는 방법을 얻었고요

· 7장의 문제들 — 직선의 방정식, 평면의 방정식, 수직인 벡터 찾기 — 이 전부 연립방정식으로 귀결됩니다

그러니까 7장은 앞에서 만든 도구를 기하 문제에 갖다 쓰는 장입니다. 순서가 거꾸로였다면 벡터 문제를 풀 방법이 없었을 거예요.

교재의 장 순서에도 이유가 있다는 것이고, 진도를 그대로 따라가는 이 기록이 그 순서를 그대로 통과하고 있다는 뜻이기도 합니다. 5회차에서 이야기한 "진도가 정해져 있다는 것의 이점"이 여기서 한 번 더 확인됩니다.

그리고 8장에서는 다시 추상으로 올라갑니다. 지금까지 다룬 것들이 사실 더 큰 규칙의 특수한 경우였다는 이야기로요. 10회차에서 만나게 됩니다.

📌 좌표계를 고르는 일

이 장에서 한 가지만 더 짚겠습니다. 성분으로 적으려면 기준을 먼저 정해야 한다는 점이요.

벡터 자체는 화살표입니다. 어디에 놓든 방향과 길이가 같으면 같은 벡터예요. 그런데 성분으로 적으려면 어느 방향을 가로로 볼지, 어느 방향을 세로로 볼지 정해야 합니다. 기준이 달라지면 같은 화살표가 다른 숫자로 적힙니다.

그래서 성분 표기는 벡터의 성질이 아니라 우리가 고른 관점의 결과입니다. 이걸 알고 있으면 나중에 두 가지가 자연스러워져요.

· 기준을 바꾸면 계산이 쉬워지는 경우 — 비스듬한 문제를 축에 맞춰 돌려놓으면 훨씬 간단해집니다. 회전 변환이 하는 일이 그거예요

· 기준이 달라 값이 안 맞는 경우 — 3차원 작업에서 좌표계 방향이 도구마다 달라 모델이 뒤집혀 들어오는 일이 흔합니다

그리고 이건 데이터 쪽에서도 그대로 나타납니다. 같은 데이터를 어떤 축으로 볼 것인가를 바꾸면 훨씬 적은 숫자로 표현할 수 있게 되고, 그게 차원 축소라고 부르는 작업입니다. 결국 좋은 기준을 찾는 일이에요.

"성분으로 적어두면 계산이 싸진다"는 이번 회차의 주제에는 "어떤 기준으로 적을 것인가"가 따라옵니다. 8장에서 이 이야기가 정의로 정리됩니다.

🎯 내적이 유사도가 되는 과정

이 장에서 배우는 계산이 지금 가장 많이 쓰이는 자리를 하나만 더 짚겠습니다.

문장 두 개가 비슷한지 판정한다고 해봅시다. 글자를 비교해서는 안 됩니다. "강아지가 뛴다"와 "개가 달린다"는 글자가 하나도 안 겹치는데 뜻은 비슷하니까요.

그래서 요즘 방식은 문장을 벡터로 바꿉니다. 뜻이 비슷하면 방향이 비슷해지도록요. 그다음에 두 벡터의 사이각을 재면 뜻의 비슷함이 숫자로 나옵니다.

여기서 쓰는 계산이 저자가 푼 그 내적입니다. 다만 조건이 하나 붙어요. 길이를 1로 맞춰두고 비교합니다.

이유가 있습니다. 긴 문장은 벡터의 길이가 길어지는 경향이 있는데, 길이가 길다고 뜻이 더 강한 건 아니거든요. 그래서 방향만 남기고 길이를 지웁니다. 그러면 내적 값이 그대로 코사인 값이 되고, 1에 가까울수록 비슷합니다.

교재의 "사이각을 구하라"가 실무에서는 "이 둘이 얼마나 비슷한가"로 나타나는 것이죠. 그리고 이건 검색, 추천, 문서 분류에서 매일 도는 계산입니다.

4기 1차 5회차의 「선형대수와 AI」와 이 편을 같이 읽으면, 왜 그 분야에서 이 과목을 계속 이야기하는지가 분명해집니다.

그리고 규모를 생각하면 이 계산이 왜 그렇게 중요한지도 보입니다. 문서 백만 개 중에서 비슷한 것을 찾으려면 내적을 백만 번 해야 하거든요. 그래서 이 단순한 계산을 얼마나 빨리 하느냐가 검색 속도를 좌우하고, 전용 하드웨어까지 동원됩니다. 교재에서 손으로 몇 번 해보는 그 계산이, 실제로는 초당 수억 번 도는 계산이에요. 단순해서 중요한 경우입니다.

📝 원문에서는 이렇게 씁니다

문제 목록만 봐도 이 장의 흐름이 보입니다.

1. 평면벡터 A와 B에 대해 (2A+3B)+(-3)(-A+B)를 성분으로 표시하라
4. 두 점을 지나는 직선의 벡터방정식을 구하고, 이를 성분 표현으로도 구하라
10. 다음에 주어진 두 벡터의 사이각을 구하라

성분으로 바꾸기 → 벡터로 적기 → 각도 구하기 순서입니다. 표기를 먼저 익히고, 그 표기로 도형을 적고, 그다음에 계산으로 정보를 뽑아내는 흐름이에요.

그리고 4번 문제가 특히 좋습니다. "벡터방정식을 구하고, 이를 성분 표현으로도 구하라." 같은 것을 두 가지로 적어보게 하는 것이죠. 표기를 바꾸는 연습이 이 장의 핵심이라는 걸 문제가 보여줍니다.

큐레이터 노트

여덟 번째 회차인데도 형식이 그대로인 기록이라 뽑았습니다. 중간 단계를 건너뛰지 않는 것, 판정 근거를 한 줄씩 적는 것이 1회차부터 유지되고 있어요.

이번 장에서 다루는 표기 바꾸기는 이번 회차 다른 두 편과 정확히 같은 이야기이기도 합니다. 한 번 형태를 바꿔두면 그 뒤가 싸진다는 것이요.

9·10회차에서 7장 후반과 8장으로 이어집니다.

원문 읽으러 가기  ↗irwin-kr.tistory.com/25

💡 세 편을 겹쳐 읽으며

한 번 바꿔두면 나중이 싸집니다

세 편이 각자의 자리에서 같은 거래를 합니다. 지금 한 번 수고해서 나중을 싸게 만드는 것이요.

useMemo는 계산 결과를 저장해두고 다음 렌더링에서 다시 안 합니다. 클로저는 상태를 함수에 붙여두고 부를 때마다 다시 만들지 않습니다. 벡터의 성분 표기는 도형을 숫자로 옮겨두고 그다음 계산을 전부 산수로 바꿉니다.

셋 다 "미리 형태를 바꿔둔다"는 점이 같습니다. 그리고 셋 다 그 대가로 뭔가를 들고 있어야 해요. 메모리든, 참조든, 표기의 추상성이든요.

남겨둔 것에는 관리 비용이 붙습니다

그래서 셋 다 조건이 붙습니다.

useMemo의존성 목록이 정확해야 합니다. 틀리면 옛날 값이 남고, 그건 없느니만 못한 상태예요. 그리고 리액트가 버릴 수도 있어서 정확성을 여기에 걸면 안 됩니다.

클로저도 마찬가지입니다. 값이 계속 살아 있다는 건 메모리에서 안 없어진다는 뜻이에요. 편의이면서 동시에 부담입니다.

남겨두면 싸지는데, 남겨둔 것을 관리해야 합니다. 캐시를 다루는 일이 대체로 이 형태입니다.

문서와 소스를 끝까지 읽었습니다

방식에서도 겹치는 게 있습니다.

박현구 님은 문서의 주의 사항까지 읽어 옮겼습니다. 노현주 님은 문법을 배우고 나서 실제 프레임워크 소스에서 쓰이는 자리를 찾아갔고요. 최태형 님은 풀이의 중간 단계를 하나도 안 건너뛰었습니다.

셋 다 "여기까지만 알면 쓸 수 있는" 지점에서 한 발 더 갔습니다. 그리고 그 한 발에서 나온 것들이 이 회차의 내용 대부분이에요.

문서의 사용법 부분만 읽으면 훅을 쓸 수는 있습니다. 그런데 캐시가 버려질 수 있다는 걸 모르면 언젠가 재현 안 되는 버그를 만나요. 문법만 알면 카운터는 만들 수 있는데, 반복문 안에서 함수를 만들 때 왜 전부 같은 값이 나오는지는 설명 못 합니다.

"쓸 수 있다"와 "예측할 수 있다" 사이의 거리가 그 한 발입니다. 1회차에서 이 회차 주제로 삼았던 그 거리이기도 하고요.

이번 회차에서 가져갈 것

1. 메모이제이션은 성능용으로만 쓰기.

캐시된 값이 버려질 수 있다는 전제로 쓰세요. "이 값이 유지돼야 로직이 맞는다"면 상태나 참조를 써야 합니다.

2. 의존성 목록은 손으로 관리하지 않기.

계산 안에서 쓰는 값이 하나라도 빠지면 옛날 값이 남고, 에러도 안 납니다. 린터가 검사하게 두세요.

3. 객체나 배열을 의존성에 넣을 때 주의하기.

내용이 같아도 매번 새로 만들어진 것이면 다르다고 판정합니다. 메모이제이션을 걸었는데 매번 다시 계산된다면 여기부터 보세요.

4. 파이썬에서 "읽기는 되는데 고치기가 안 될" 때.

함수 안에서 대입이 있으면 그 이름은 지역 변수가 됩니다. 바깥 것을 고치려면 명시적으로 선언해야 합니다.

5. 방향이 비슷한지 볼 때는 각도 대신 내적.

0이면 수직, 양수면 같은 쪽, 음수면 반대쪽입니다. 각도를 구하지 않고도 판정이 끝나는 경우가 대부분입니다.


4기 8회차는 2026년 1월이었습니다.

리액트 문서와 파이썬 스터디와 전공 교재라는 서로 다른 자리에서, 세 분이 나란히 미리 바꿔두면 나중이 싸진다는 이야기를 남겼습니다. 그리고 셋 다 남겨둔 것에 어떤 대가가 붙는지도 함께 적었어요.

성장일지는 멤버들이 2주에 한 번씩 자신의 성장을 기록하는 활동이에요. 지난 기수의 성장일지를 다시 읽으면서, 그때 소개해드리지 못한 글을 하나씩 꺼내고 있습니다.

좋은 글 남겨주신 박현구 님, 노현주 님, 최태형 님께 감사드립니다. 그리고 4기를 함께 채워주신 모든 멤버분들께도요! 😊

다음 큐레이션으로 다시 찾아오겠습니다! 🌱

- 에디터 · 성장일지 큐레이터 -


※ 본 큐레이션은 각 저자가 공개한 글을 소개하는 것이며, 모든 원문의 저작권은 저자에게 있습니다. 저자 본인의 요청이 있을 경우 즉시 수정 또는 삭제합니다.

WRITE YOUR GROWTH

오늘의 배움을
내일의 성장으로.

기록은 나를 돌아보게 하고, 공유는 우리를 연결합니다.
당신의 다음 성장 이야기도 그로스로그에서 시작해보세요.

커뮤니티 둘러보기